Linux Socket 基础
编写服务端程序server.c
#include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netinet/in.h> #define MAXLINE 1024 int main() { int listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd == -1) { printf("socket error[%d]:%s\n", errno, strerror(errno)); exit(errno); } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(55555); if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { printf("bind error[%d]:%s\n", errno, strerror(errno)); exit(errno); } if (listen(listen_fd, 3) == -1) { printf("listen error[%d]:%s\n", errno, strerror(errno)); exit(errno); } while (1) { struct sockaddr_in client_addr; memset(&client_addr, 0, sizeof(client_addr)); int len = sizeof(client_addr); int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &len); if (client_fd == -1) { printf("accept error[%d]:%s\n", errno, strerror(errno)); continue; } char rb[100] = {0}; int recv_len = recv(client_fd, rb, MAXLINE, 0); if (recv_len == -1) { printf("recv error[%d]:%s\n", errno, strerror(errno)); continue; } printf("recv[%d]:%s\n", recv_len, rb); strcat(rb, ", has been received by server, the msg from server"); if (send(client_fd, rb, strlen(rb), 0) == -1) { printf("send error[%d]:%s\n", errno, strerror(errno)); continue; } printf("send success\n"); close(client_fd); } close(listen_fd); }
代码说明:
函数原型: int socket(int domain, int type, int protocol)中的参数protocol取值的取值见 netinet/in.h (结合参考/etc/protocols文件)
编写客户端程序client.c
#include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <netinet/in.h> #include <string.h> #define MAXLINE 1024 int main() { int client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd == -1) { printf("socket error[%d]:%s\n", errno, strerror(errno)); exit(errno); } struct sockaddr_in target_addr; memset(&target_addr, 0, sizeof(target_addr)); target_addr.sin_family = AF_INET; target_addr.sin_port = htons(55555); if (inet_pton(AF_INET, "127.0.0.1", &target_addr.sin_addr) < 0) { printf("inet_ptonl error[%d]:%s\n", errno, strerror(errno)); exit(errno); } if (connect(client_fd, (struct sockaddr *)&target_addr, sizeof(target_addr)) < 0) { printf("connect error[%d]:%s\n", errno, strerror(errno)); exit(errno); } char sb[500]={0}; strcpy(sb, "client message"); if (send(client_fd, sb, strlen(sb), 0) < 0) { printf("send error[%d]:%s\n", errno, strerror(errno)); exit(errno); } printf("send success\n"); char rb[MAXLINE]={0}; if (recv(client_fd, rb, MAXLINE, 0) < 0) { printf("recv error[%d]:%s\n", errno, strerror(errno)); exit(errno); } printf("recv from server:%s\n", rb); }
编译
$gcc client.c -o client
运行
1. 启动server
recv[14]:client message
send success
2. tcpdump观察
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
09:41:12.535453 IP localhost.30256 > localhost.55555: Flags [S], seq 4085943811, win 32792, options [mss 16396,sackOK,TS val 2860232704 ecr 0,nop,wscale 7], length 0
09:41:12.535526 IP localhost.55555 > localhost.30256: Flags [S.], seq 555658646, ack 4085943812, win 32768, options [mss 16396,sackOK,TS val 2860232705 ecr 2860232704,nop,wscale 7], length 0
09:41:12.535547 IP localhost.30256 > localhost.55555: Flags [.], ack 1, win 257, options [nop,nop,TS val 2860232705 ecr 2860232705], length 0
09:41:12.535614 IP localhost.30256 > localhost.55555: Flags [P.], seq 1:15, ack 1, win 257, options [nop,nop,TS val 2860232705 ecr 2860232705], length 14
09:41:12.535631 IP localhost.55555 > localhost.30256: Flags [.], ack 15, win 256, options [nop,nop,TS val 2860232705 ecr 2860232705], length 0
09:41:12.535718 IP localhost.55555 > localhost.30256: Flags [P.], seq 1:65, ack 15, win 256, options [nop,nop,TS val 2860232705 ecr 2860232705], length 64
09:41:12.535728 IP localhost.30256 > localhost.55555: Flags [.], ack 65, win 257, options [nop,nop,TS val 2860232705 ecr 2860232705], length 0
09:41:12.535753 IP localhost.55555 > localhost.30256: Flags [F.], seq 65, ack 15, win 256, options [nop,nop,TS val 2860232705 ecr 2860232705], length 0
09:41:12.535902 IP localhost.30256 > localhost.55555: Flags [F.], seq 15, ack 66, win 257, options [nop,nop,TS val 2860232705 ecr 2860232705], length 0
09:41:12.535921 IP localhost.55555 > localhost.30256: Flags [.], ack 16, win 256, options [nop,nop,TS val 2860232705 ecr 2860232705], length 0
3. 启动client
send success
recv from server:client message, has been received by server, the msg from server
以上三步,把结果一起贴出来了,需要启动三个终端,并且按顺序运行以上三个命令
观察监听状态
tcp 0 0 0.0.0.0:55555 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:55555 127.0.0.1:30256 TIME_WAIT
分析
1. server执行完lisetn函数的时候, 开始监听
2. server 执行到accept的时候,阻塞了,等待client的连接
3. client执行connect的时候,client发起三次握手
tcpdump捕捉到三次握手的信息
10:03:51.122096 IP localhost.55555 > localhost.30259: Flags [S.], seq 3786610237, ack 261830420, win 32768, options [mss 16396,sackOK,TS val 2861591291 ecr 2861591291,nop,wscale 7], length 0
10:03:51.122116 IP localhost.30259 > localhost.55555: Flags [.], ack 1, win 257, options [nop,nop,TS val 2861591291 ecr 2861591291], length 0
tcp 0 0 127.0.0.1:30259 127.0.0.1:55555 ESTABLISHED
tcp 0 0 127.0.0.1:55555 127.0.0.1:30259 ESTABLISHED
server accept的阻塞解除,执行到下一行
4. client继续执行,执行完send函数的时候, 发生了client到server的数据交互
10:19:21.731810 IP localhost.55555 > localhost.30259: Flags [.], ack 15, win 256, options [nop,nop,TS val 2862521901 ecr 2862521901], length 0
5. server继续执行,执行recv的时候,从内核缓存中取出client发过来的数据(tcpdump并没有变化可以看出是在缓存中接收)
6. server继续执行,执行完send函数发消息给client的时候,tcpdump捕捉到server到client的数据交互
10:23:41.110764 IP localhost.30259 > localhost.55555: Flags [.], ack 65, win 257, options [nop,nop,TS val 2862781280 ecr 2862781280], length 0
7. server继续执行,执行到close(client_fd)的时候,tcpdump捕捉到断开的握手信息
10:24:36.775306 IP localhost.30259 > localhost.55555: Flags [.], ack 66, win 257, options [nop,nop,TS val 2862836945 ecr 2862836905], length 0
网络状态变为
tcp 65 0 127.0.0.1:30259 127.0.0.1:55555 CLOSE_WAIT
tcp 0 0 127.0.0.1:55555 127.0.0.1:30259 FIN_WAIT2
8. 此时, 关闭了server -> client方向的数据流
9. client继续执行,执行完recv函数,获取到server发送过来的数据(依然从缓存中得到)
10. client运行完close(client_fd)函数, 发起断开握手
10:25:34.171108 IP localhost.55555 > localhost.30259: Flags [.], ack 16, win 256, options [nop,nop,TS val 2862894340 ecr 2862894340], length 0
11. 网络状态变成
tcp 0 0 127.0.0.1:55555 127.0.0.1:30259 TIME_WAIT
状态变迁
建立连接时的状态变迁
一开始,建立连接之前服务器和客户端的状态都为CLOSED。服务器创建socket后开始监听,变为LISTEN状态。客户端请求建立连接,向服务器发送SYN报文,客户端的状态变为SYN_SENT。服务器收到客户端的报文后向客户端发送ACK和SYN报文,此时服务器的状态变为SYN_RCVD。然后,客户端收到ACK、SYN,就向服务器发送ACK,客户端状态变为ESTABLISHED,服务器收到客户端的ACK后也变为ESTABLISHED。此时,3次握手完成,连接建立!
断开连接时的状态变迁
由于tcp连接是全双工的,断开连接会比建立连接麻烦一点点。客户端先向服务器发送FIN报文,请求断开连接,其状态变为FIN_WAIT1。服务器收到FIN后向客户端发生ACK,服务器状态变为CLOSE_WAIT。客户端收到ACK后就进入FIN_WAIT2状态。此时连接已经断开了一半了。如果服务器还有数据要发送给客户端,就会继续发送。直到发完了,就发送FIN报文,此时服务器进入LAST_ACK状态。客户端收到服务器的FIN后,马上发送ACK给服务器,此时客户端进入TIME_WAIT状态,再过了2MSL长的时间后进入CLOSED状态。服务器收到客户端的ACK就进入CLOSED状态。
至此,还有一个状态没有提及:CLOSING状态。CLOSING状态表示客户端发生了FIN,但没有收到服务器的ACK,却收到了服务器的FIN。这种情况发生在服务器发送的ACK丢包的时候,因为网络传输有时会有意外。