简单的tcp socket编程及分析

最近没事干研究了一下socket编程,自己写了一个简单的tcp server和tcp client,发现对tcp协议有了很多新的理解。

废话不说,先上代码:

tcp_client.c:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/socket.h>  /*  socket, connect, etc. */
 4 #include <arpa/inet.h>  /*  inet_aton, sockaddr_in, etc */
 5 #include <string.h>  /*  memset */
 6
 7 #define BUFFSIZE 1024
 8 typedef  struct  sockaddr SA;
 9
10 int  main(int  argc, char ** argv)
11 {
12     int  clientfd, port = 1234 ;
13     struct  sockaddr_in server_sockaddr;
14     char  buf[BUFFSIZE];
15     const  char * server_ip = "10.0.2.15" ;
16     int  received = 0 ;
17     int  errcode = 0 ;
18     /*  create stream socket */
19     if  ((clientfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0 ) {
20         printf("failed to create socket /n " );
21         exit(-1);
22     }
23
24     /*  zeroize and set server socket address */
25     memset(&server_sockaddr, 0 , sizeof (struct  sockaddr_in));
26     server_sockaddr.sin_family = AF_INET;
27     server_sockaddr.sin_port = htons(port);
28     inet_aton(server_ip, &server_sockaddr.sin_addr);
29
30     /*  connect client to server */
31     /*  connect: sends SYN, when server returns SYN,ACK, it sends ACK and returns */
32     if ((errcode = connect(clientfd, (SA*)&server_sockaddr, sizeof (server_sockaddr))) == 0 ) {
33         printf("connected. /n " );
34     }
35     else  {
36         fprintf(stderr , "error! connection refused. /n " );
37         exit(-1);
38     }
39
40     /*  send message */
41     while  ((fgets(buf, BUFFSIZE, stdin )) != NULL ) {
42         /*  wirte: PSH(push), ACK and send data */
43         write(clientfd, buf, strlen(buf)-1); /*  do not send the trailing '/n' */
44         //printf( "haha" );
45     }
46     /*  close: FIN, ACK */
47     close(clientfd);
48
49     return  0 ;
50 }

tcp_server.c:

 1 #include <stdio.h>
 2 #include <stdlib.h>  /*  exit */
 3 #include <sys/socket.h>  /*  socket, connect, etc. */
 4 #include <arpa/inet.h>  /*  inet_aton, sockaddr_in, etc */
 5 #include <string.h>  /*  memset */
 6
 7 typedef  struct  sockaddr SA;
 8 #define LISTENQ 1024
 9 #define BUFFSIZE 1024
10
11 int  main(int  argc, char ** argv)
12 {
13     int  listenfd, commfd, port = 1234 , clientlen, optval=1 ;
14     char  msg[BUFFSIZE];
15     struct  sockaddr_in server_sockaddr, client_sockaddr;
16
17     clientlen = sizeof (client_sockaddr);
18     /*  create stream socket */
19     if  ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP)) < 0 ) {
20         fprintf(stderr , "error! cannot create socket. /n " );
21         exit(-1);
22     }
23
24     /*  zeroize and fill server sockaddr */
25     memset(&server_sockaddr, 0 , sizeof (server_sockaddr));
26     server_sockaddr.sin_family = AF_INET;
27     server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
28     server_sockaddr.sin_port = htons(port);
29
30     /*  configure the server so that it can be restarted immediately */
31     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const  void *)&optval, sizeof (int ));
32
33     /*  bind socket to inaddr */
34     bind(listenfd, (SA *)&server_sockaddr, sizeof (server_sockaddr));
35
36     /*  listen on listenfd */
37     listen(listenfd, LISTENQ);
38
39
40     while (1 ) {
41         /*  accept incoming requests */
42         /*  accpet: SYN, ACK */
43         commfd = accept(listenfd, (SA* )&client_sockaddr, &clientlen);
44         /*  read: ACK */
45         while (read(commfd, msg, BUFFSIZE) > 0 ) {
46             printf("received: /" %s /"/n " , msg);
47             fflush(stdout );
48         }
49         printf("EOF encountered /n " );
50         /*  close: FIN ACK */
51         close(commfd);
52     }
53
54
55     return  0 ;
56 }

讨论:

tcp client/server 的一般编写步骤:

tcp client的套路:socket -> connect -> send/receive data -> close

tcp server的套路:socket -> bind -> listen -> accept -> send/receive data -> close

在编写tcp client时,首先使用socket函数创建一个新的socketfd(socket file descriptor)。socket函数的第一个参数永远是AF_INET(Address Family Internet),表示为一个internet socket;第二个参数为该socket的类型(这里为SOCK_STREAM,即流类型(类似于TCP型);第三个参数为协议类型(在ip包中的 8bit protocol type),这个在/usr/include/linux/in.h中有定义,IPPROTO_IP的值为0,其实就是TCP. 另外还有IPPROTO_TCP, IPPROTO_UDP等等。

打开socketfd后,我们将服务器的ip address,port(合称socket address)填入一个sockaddr_in结构体中,并调用connect函数。

connect函数是我们这里的重头戏。如果使用wireshark来抓包可以发现,当client程序运行到connect时,会发出一个 (SYN),而server会返回一个(SYN ACK)表示已收到(SYN),最后connect函数会发出一个(ACK)表示已收到(ACK)并返回0。这就是通常说的tcp中的三次握手 (three-way handshake).接下来我们就可以对socketfd进行读写操作了(也就是对服务器发/收数据)。

这里另外提一下,如果server根本不通(ping不到),则在抓包的时候一段时间后会看到ICMP包,内容为错误(destination unreachable),connnect返回-1. 如果server可以连通,但是没有开放相应的端口(这里为1234),则(SYN)还是会发出,而server会返回一个(ACK RST),表示拒绝连接,connect仍然返回-1。由此可以知道,只有在物理上能够连接的基础上,才会发出(SYN)的请求.

当connect返回0时(同时server的accept函数返回0),我们可以说连接已经建立了。这时候就可以收发数据了。我们在这两个例子里 面用的是裸unix系统调用(read和write)来对socketfd进行操作。当写操作完成时,我们可以使用close()来关闭连接,这时 client发出(FIN ACK),server在接收到之后会发出(ACK)来关闭连接。(这种情况为client发出的关闭连接).

我们再来看server端:首先还是调用socket函数来创建socket, 然后将server的ip,port填入一个sockaddr_in结构。接下来使用bind()将sockaddr与socket进行绑定(对比 client程序中为connect),接着使用listen使该socket变为listen模式(默认socket为主动连接模式)。这时使用 accept()函数。accept函数的功能就是一直等待某个端口的连接,当收到(SYN)请求时回复(SYN ACK)并等待,然后当收到(ACK)时返回。与前面client的讨论联系起来我们就能够完整了解连接的建立过程。

当连接建立以后,后面的操作就和client相似了:调用read/write进行数据的发送/接收,调用close()进行连接的断开。这种情况下为服务器主动断开连接,发送FIN ACK,而client接受以后回复ACK关闭连接.

你可能感兴趣的:(server,Stream,socket,tcp,internet,Descriptor)