Linux Socket 基础

Usper 2015-03-24

编写服务端程序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);
}

编译

linux 命令 
$gcc server.c -o server
$gcc client.c -o client

运行

1. 启动server

$./server
recv[14]:client message
send success

2. tcpdump观察

#tcpdump -i any tcp port 55555
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

$./client
send success
recv from server:client message, has been received by server, the msg from server

以上三步,把结果一起贴出来了,需要启动三个终端,并且按顺序运行以上三个命令

观察监听状态

$netstat -an|grep 55555
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函数的时候, 开始监听

tcp 0 0 0.0.0.0:55555 0.0.0.0:* LISTEN

2. server 执行到accept的时候,阻塞了,等待client的连接

3. client执行connect的时候,client发起三次握手

tcpdump捕捉到三次握手的信息

tcpdump结果 
10:03:51.122059 IP localhost.30259 > localhost.55555: Flags [S], seq 261830419, win 32792, options [mss 16396,sackOK,TS val 2861591291 ecr 0,nop,wscale 7], length 0
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
握手完成后,网络状态变成
netstat -an|grep 55555 结果 
tcp 0 0 0.0.0.0:55555 0.0.0.0:* LISTEN
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的数据交互

tcpdump 
10:19:21.731775 IP localhost.30259 > localhost.55555: Flags [P.], seq 1:15, ack 1, win 257, options [nop,nop,TS val 2862521901 ecr 2861591291], length 14
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的数据交互

tcpdump 
10:23:41.110728 IP localhost.55555 > localhost.30259: Flags [P.], seq 1:65, ack 15, win 256, options [nop,nop,TS val 2862781280 ecr 2862521901], length 64
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捕捉到断开的握手信息

tcpdump 
10:24:36.735278 IP localhost.55555 > localhost.30259: Flags [F.], seq 65, ack 15, win 256, options [nop,nop,TS val 2862836905 ecr 2862781280], length 0
10:24:36.775306 IP localhost.30259 > localhost.55555: Flags [.], ack 66, win 257, options [nop,nop,TS val 2862836945 ecr 2862836905], length 0

网络状态变为

netstat -an |grep 55555
tcp 0 0 0.0.0.0:55555 0.0.0.0:* LISTEN
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)函数, 发起断开握手

tcpdump结果 
10:25:34.171070 IP localhost.30259 > localhost.55555: Flags [F.], seq 15, ack 66, win 257, options [nop,nop,TS val 2862894340 ecr 2862836905], length 0
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 0.0.0.0:55555 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:55555 127.0.0.1:30259 TIME_WAIT
 
12. tcp状态变迁说明

Linux  Socket 基础
 
http://blog.csdn.net/engrossment/article/details/8104482

状态变迁
建立连接时的状态变迁
    一开始,建立连接之前服务器和客户端的状态都为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丢包的时候,因为网络传输有时会有意外。

相关推荐