Socket之TCP相关

客户端socket简单示例(以下都是针对TCP协议)

#define BUF_SIZE 100

int socket = socket(AF_INET,SOCK_STREAm,IPPROTO_TCP);
struct sockaddr_in sockAddr;
sockAddr.sn_famil = AF_INET;
sockAddr.sn_addr.s_addr = inet_addr(127.0.0.1);//处理网络字节序,大小端差异
sockAddr.sn_port = htons(1234);

connect(socket,(struct sockAddr *)&sockAddr, sizeof(sockAddr) );
char buffer[BUF_SIZE];
read(socket,buffer,sizeof(BUF_SIZE)-1);

close(socket);

服务端sockt简单示例

AF_INET:代表ipv4地址;SOCK_STREAM:数据传输方式,代表面向连接的数据传输方法;IPPROTO_TCP:指定协议
int socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
struct sockAddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));//清理内存
sockAddr.sin_famil = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr(127.0.0.1);
sockAddr.sin_port = htons(1234);

bind(socket,(struct sockAddr_in*)sockAddr,sizeof(sockAddr));//绑定socket描述符,把组合体特定地址分配给socket。
listen(socket,20);//socket是对应描述符,20 是可以排队的最大连接数
sickle_t  clnt_addr_size = sizeof(clnt_addr);
int clinSocket =   accpect(socket, (struct sockaddr*)&serv_addr,clnt_addr_size);//获取客户端的socket
char str[] = "你好,客户端";
write(socket, str,sizeof(str));
close(clinSocket);
close(socket);

客户端流程

  1. 客户端创建socket,先调socket函数创建个socket返回socket描述符,它存在于协议族空间中没有具体地址。
  2. 声明结构体sockAddr_in,赋值地址族或称协议域,IP地址,端口,用来识别主机中的进程。
  3. conect连接服务器(这里开始三次握手连接);
  4. 然后可以使用下面函数进行发送接收数据了
  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()
  1. close() 使用后可以关闭数据了(这里开始四次握手断开连接)。

服务端流程

  1. 客户端创建socket,先调socket函数创建个socket返回socket描述符,它存在于协议族空间中没有具体地址。
  2. 声明结构体sockAddr_in,赋值地址族或称协议域,IP地址,端口,用来识别主机中的进程。
  3. 调用bind()函数把一个地址族中的特定地址赋给socket,如:把AF_INET,SOCK_STREAM,IPPROTO_TCP组合赋值给socket。如果没有调用过bind()这个函数,客户端调用connect() 或者服务端调用listen()监听函数,系统会自动分配一个端口号和自身的IP地址组合,用于提供服务,而客户端就不用指定,这也是服务端代码要调bind()函数而客户端不用的原因。(客户端不用bind函数)。
  4. 调用listen(),开始监听上面创造的socket。
  5. 当socket处于listen状态,调用accept()程序就会进入阻塞状态,直到接受到客户端的connect()请求。accept() 会返回一个和客户端有关的socket描述字符,然后可以对这个字符进行各种读写操作,便是向客户端发送数据。
    6.使用完成后便可以close关闭自己创建的socket和accept返回的socket

注:close是将socket从内存中清楚,还有种方式shutdown,用来断开连接,但是不会清除socket,函数原型int shutdown(int sock, int howto),howto参数表示断开方式。在linux下有以下几个方式。

  • SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。其他平台稍有不同。
  • SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
  • SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以
  • SHUT_RD 为参数,另一次以 SHUT_WR 为参数

三次握手建立连接

首先得有个服务端处于监听socket的状态,accept阻塞着线程等待客户端的请求。
下图是网上找的TCP数据报结构,握手时的数据包格式


TCP数据报结构

三次握手中,需要对TCP头部的这几个概念理解

  1. seq表示包的序号。
  2. ack来确认对方发送的某个序号seq收到了,采用ack = seq + 1的方式
  3. URG,ACK,PSH,PST,SYN,FIN这6个bit表示标志,SYN表示是同步连接数据包,FIN是否断连数据包。所以四次断连发送的就是FIN包。

第一次:客户端调用connect()向目标socket发起连接请求,这一步客户端socket会组织一个数据包比如:ACK标志位不设置,设置SYN标志位(Synchronous同步的意思,同步建立连接)标志位 =1,seq = x,进入SYN-SEND(同步已发送)状态,发送给服务端,此时connect()函数会进入阻塞状态,等待服务端的返回。


第一次握手数据包

第二次:服务端接受到客户端的请求和数据包后,组织数据包,SYN置1,ACK标志位(Acknowledge确认的意思)置1, 同时生成一个确认序号ack= x+1 ,同时随机选择seq = y进入SYN-RCVD(同步已发送)状态。


第二次握手数据包

第三次:客户端接受到了服务端的确认连接请求数据包,此时connect会返回,进入连接状,组织数据包:SYN标志位不设置,ACK置1,确认号ack = y+1,seq= x + 1(我第二次发给你包),再次发送给服务端,表示已经知道你已经收到我想连接的请求并且可以连接了。此时服务端收到数据后,进入已建立连接的状态,accept()函数会返回一个socket 描述符,即,我们在服务端进行读写操作的socket,这样连接就完成了。


第三次握手TCP数据包
三次握手连接示意图

注:

seq的作用是标识数据包序号,ack的作用是确认对方发送相应seq序号的数据包是否收到,把客户端发的seq加上1赋值给ack,然后返回给客户端表示已经收到你的连接请求,同时自己根据ack的值,再次组织下一个按序号应该发送的包,给seq赋值,发送给客户端,客户端收到数据后,查看ack是否是自己第一次发送数据包里的seq加上1的值,来确认服务端有没有收到自己的连接请求,然后客户端重新组织数据包,把服务端数据包里的seq加上1 赋值给自己的ack确认号,发送给服务端,表示我已经知道你收到我发送连接的请求了。
这个过程中若是收到服务的数据包ack并不是上次自己发送seq加上1,则会认为服务端没有收到数据,会重新组织数据再次发送,直到接到服务端包含正确的seq数据包或者等待数据包时间超时失效。

四次握手断开连接

  1. 客户端调用close()函数后,向服务端发送FIN数据包(设置了FIN标志位为1),客户端进入第一等待阶段。

  2. 服务器收到数据包后,检测到设置FIN标志位,知道要断开连接了,向客户端发送一个确认包,服务端进入关闭等待阶段。并不是立即关闭,只是先向客户端发送确认包,告诉客户端,我知道要断开了,准备些事情。此时,客户端收到确认包后,等待服务器准备完毕再次给自己发送数据包,这时客户端进入第二等待阶段。

  3. 过一段时间后,服务器准备完成,可以断开连接了,于时再次发送FIN包,告诉客户端,准备好了,可以断开连接了,这时服务端进入一个LAST_ACK的状态(猜测时只能接收一个ACK包的状态)。

  4. 客户端收到服务端的FIN数据包后,再次向服务端发送ACK包,告诉服务端,断开吧,就进入了TIME_WAIT状态(注意,此时客户端并不是立即断开连接,而是会等待一会,因为会存在数据包丢失的情况,若是服务端没有收到自己最后发送的数据包,则服务端还会向客户端发送FIN_2数据包,等待时间,与数据包在网络中的有效时限有关)。此时,服务端接受到数据后会关闭Socket进入Close状态。客户端等了一段时间后仍没有接到FIN_2的数据包,就会认为,服务端已经接到自己最后一次发送的数据包,这时客户端才会进入CLOSE的状态。


    四次断连图

socket缓冲区以及阻塞模式

socket缓冲区

每个socket创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()和send() 并不是立即向网络中传输数据,而是先将数据写入缓冲区中,由TCP协议将数据发送到目标机器,一旦数据写入缓冲区,函数就可以成功返回,不管有没有到达目标机器,也不管他们何时被发送到网络,这些都是TCP负责的事情。
这些I/O缓冲区特性如下:

  • I/O缓冲区在每个TCP套接字中单独存在
  • I/O缓冲区在创建套接字时会自动生成
  • 即使关闭套接字也会继续传送输出缓冲区中遗留的数据。
  • 关闭套接字将丢失输入缓冲区中的数据


    四次握手断连示意图.jpg

阻塞模式

对于TCP套接字,当使用write()/send()发送数据时:

  1. 首先会检查缓冲区,若缓冲区的剩余空间小雨要发送的数据时,write()/send()进入阻塞模式,将会等待数据发送到目标机器,腾出足够空间,才会唤醒write()/send()函数继续写入数据。
  2. 如果TCP协议正在向网络发送数据,那么输出缓冲区将会被锁定,不允许写入,write/send()函数写入数据。
  3. 如果要写入的数据大于缓冲区的最大长度,那么将会分批写入。
  4. 直到所有数据被写入缓冲区 write()/send() 才能返回。
    当使用read和recv读取数据时。
  5. 手写会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。
  6. 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能不一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到read()/recv()函数才会返回,否则就一直被阻塞。
  7. 直到读取到数据后 read()/recv()才会返回,否则就一直被阻塞。

所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,183评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,850评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,766评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,854评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,871评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,457评论 1 311
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,999评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,914评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,465评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,543评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,675评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,354评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,029评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,514评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,616评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,091评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,685评论 2 360