图说使用socket建立TCP连接

在网络应用如火如荼的今天,熟悉TCP/IP网络编程,那是最好不过。如果你并不非常熟悉,不妨花几分钟读一读。 为了帮助快速理解,先上个图,典型的使用socket建立和使用TCP/UDP连接过程为(截图来源戳这里):

下面仅讲述TCP连接建立的过程。 (参考资料来自这里)

1. Initial State (初始阶段)

o TCP is connection based, ... establishing it is a complex multistage process
o initially all machines are the same
o no special 'server' machines
o the difference is all in the software
  • TCP是面向连接的协议,TCP连接的建立是一个复杂的多阶段的过程
  • 最开始所有机器状态都是一样的
  • 并不存在所谓的'server'机器
  • 所有的区别仅存在于软件之中

2. Passive Open (被动Open)

o server process does a 'passive' open on a port
o it waits for a client to connect
o at this stage there is no Internet network traffic
o tells the TCP layer which process to connect to
  • 服务器端进程在某个端口上执行一个"被动"open
  • 服务器端进程等待某个客户端来连接
  • 这一阶段并不存在Internet网络传输
  • 告知TCP层哪一个进程可以接受连接

3. Active Open (主动Open)

o client process usually on a different machine
o performs an 'active' open on the port
o port number at the client end is needed
          usually automatic (e.g. 2397)
          but can be chosen
o network message -> server machine
          requests connection
  • 客户端进程通常情况下运行在另外一个机器上
  • 客户端进程在某个端口上执行一个"主动"Open
  • 在客户端,端口号也是需要的,通常是自动分配的(例如:2397),也可以主动选择一个端口号
  • 网络消息->服务器机器,需要一个连接

4. Rendezvous (集结,也就是连接建立)

o server side accepts and TCP connection established
o a bi-directional reliable byte-stream
o connection identified by both host/port numbers
  e.g. <151.10017.25:2397/161.112.192.5:21>
o server port is not consumed
o can stay 'passive' open for more connections
o like telephone call desk: one number many lines
  • 服务器端接受连接,TCP连接得以建立
  • 连接是一个双向的可靠字节流(即全双工可靠字节流)
  • 识别一个连接,可以用host/port对,例如: 151.10017.25:2397/161.112.192.5:21
  • 服务器端口并没有被消耗殆尽
  • 服务器端可以一直处于"被动"open状态以接收更多的连接请求
  • 类似于电话呼叫台: 一个号码多条线路

5. More

o other clients can connect to the same port
o state for connections in the client/server only
o no information needed in the network
  not like old style relay-based exchanges
o server can restrict access to specified host or port
o server can find out connected host/port
  • 其他客户端可以连接到同一个端口
  • 连接状态仅存在于client/server中
  • 与老式风格的基于中继的交换有所不同,tcp连接网络中不需要信息
  • 服务器可以对指定的主机或端口进行访问限制
  • 在服务器上可以找出已经连接的主机/端口

有关Passive open和Active open,区别如下:

passive - patient but lazy
 active - industrious but impatient

passive : waits for request for connection
        : waits for ever
 active : sends out request for connection
        : times out

o normally server does passive open
  waiting for client
o but not always (e.g. ftp)
o active opens can rendezvous ... but may miss due to time-outs
o either can specify local port
  but if not specified, allocated automatically

到此为止,我们已经弄明白了TCP连接建立的过程。下面给出一个简单的TCP client/server实现。

1. libfoo.h

 1 #ifndef _LIBFOO_H
 2 #define _LIBFOO_H
 3 
 4 #ifdef __cplusplus
 5 extern "C" {
 6 #endif
 7 
 8 #define PORT 2345
 9 
10 extern int send_file(int sockfd, char *dstfile, char *srcfile);
11 extern int recv_file(int sockfd);
12 
13 #ifdef __cplusplus
14 }
15 #endif
16 
17 #endif /* _LIBFOO_H */

2. libfoo.c

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 #include <fcntl.h>
  6 #include <sys/stat.h>
  7 #include <sys/types.h>
  8 #include <arpa/inet.h>
  9 #include "libfoo.h"
 10 
 11 typedef struct file_header_s {
 12         char *file[1024]; /* (dst) file absolute path */
 13         size_t size;      /* file size */
 14         mode_t mode;      /* file mode */
 15 } file_header_t;
 16 
 17 /*
 18  * send file via sockfd
 19  */
 20 int
 21 send_file(int sockfd, char *dstfile, char *srcfile)
 22 {
 23         int             fd;
 24         file_header_t   fhr;
 25         struct stat     sb;
 26         int             rc;
 27         int             n, m;
 28         size_t          count;
 29         char            buf[1024] = { 0 };
 30 
 31         /* open src file */
 32         if ((fd = open(srcfile, O_RDONLY)) < 0) {
 33                 fprintf(stderr, "cannot open `%s' for reading: %s\n",
 34                     srcfile, strerror(errno));
 35                 return 1;
 36         }
 37 
 38         /* stat src file */
 39         if ((rc = fstat(fd, &sb)) < 0) {
 40                 fprintf(stderr, "cannot stat fd %d: %s\n",
 41                     fd, strerror(errno));
 42                 rc = 2;
 43                 goto done;
 44         }
 45 
 46         rc = 0;
 47 
 48         /*
 49          * create dst file header and send out
 50          * o dst file path != src file path in case they are on the same host
 51          * o dst file size == src file size
 52          * o dst file mode == src file mode
 53          */
 54         memset(&fhr, 0, sizeof (fhr));
 55         strncpy((char *)fhr.file, dstfile, strlen(dstfile));
 56         fhr.size = htonl(sb.st_size);
 57         fhr.mode = htonl(sb.st_mode & ~S_IFMT);
 58 
 59         /* write file header to sockfd */
 60         if ((n = write(sockfd, &fhr, sizeof (fhr))) == -1) {
 61                 fprintf(stderr, "cannot write file header to sock %d: %s\n",
 62                     sockfd, strerror(errno));
 63                 rc = 3;
 64                 goto done;
 65         }
 66 
 67         /* read from fd, write to sockfd */
 68         count = sb.st_size;
 69         while (count > 0) {
 70                 m = (count >= sizeof (buf)) ? sizeof(buf) : count;
 71 
 72                 memset(buf, 0, sizeof (buf));
 73                 if ((n = read(fd, buf, m)) == -1) {
 74                         fprintf(stderr, "fail to read %d bytes from fd %d\n",
 75                             m, fd);
 76                         rc = 4;
 77                         goto done;
 78                 }
 79 
 80                 if ((n = write(sockfd, buf, m)) == -1) {
 81                         fprintf(stderr, "fail to write %d bytes to sock %d\n",
 82                             m, sockfd);
 83                         rc = 5;
 84                         goto done;
 85                 }
 86 
 87                 count -= m;
 88         }
 89 
 90 done:
 91         close(fd);
 92         return rc;
 93 }
 94 
 95 /*
 96  * read from sockfd then save to file
 97  */
 98 int
 99 recv_file(int sockfd)
100 {
101         int             fd;
102         file_header_t   fhr;
103         char            *file = NULL;
104         size_t          filesize;
105         mode_t          filemode;
106         size_t          count;
107         int             rc;
108         int             n, m;
109         char            buf[1024] = { 0 };
110 
111         /* 1. read file header */
112         if ((n = read(sockfd, &fhr, sizeof (fhr))) == -1) {
113                 fprintf(stderr, "fail to read file header from sock %d: %s\n",
114                     sockfd, strerror(errno));
115                 return 1;
116         }
117         file = (char *)fhr.file;
118         filesize = ntohl(fhr.size);
119         filemode = ntohl(fhr.mode);
120         printf("> dst filename=%s, filesize=%u, filemode=%o\n",
121             file, filesize, (int)filemode);
122 
123         /* 2. open file to save */
124         (void) umask(0);
125         if ((fd = open(file, O_RDWR|O_CREAT|O_TRUNC, filemode)) < 0) {
126                 fprintf(stderr, "cannot create `%s' for writing: %s\n",
127                     file, strerror(errno));
128                 return 2;
129         }
130 
131         rc = 0;
132 
133         /* 3. read from sockfd and write to fd */
134         count = filesize;
135         while (count > 0) {
136                 m = count >= sizeof (buf) ? sizeof(buf) : count;
137 
138                 memset(buf, 0, sizeof (buf));
139                 if ((n = read(sockfd, buf, m)) == -1) {
140                         fprintf(stderr, "fail to read %d bytes from sock %d\n",
141                             m, sockfd);
142                         rc = 4;
143                         goto done;
144                 }
145 
146                 if ((n = write(fd, buf, m)) == -1) {
147                         fprintf(stderr, "fail to write %d bytes to file %d\n",
148                             m, fd);
149                         rc = 5;
150                         goto done;
151                 }
152 
153                 count -= m;
154         }
155 
156 done:
157         close(fd);
158         return rc;
159 }

3. server.c

  1 /**
  2  * server.c - single connection tcp server
  3  */
  4 
  5 #include <stdio.h>
  6 #include <unistd.h>
  7 #include <errno.h>
  8 #include <string.h>
  9 #include <sys/types.h>
 10 #include <sys/types.h>
 11 #include <sys/stat.h>
 12 #include <fcntl.h>
 13 #include <sys/socket.h>
 14 #include <arpa/inet.h>
 15 #include "libfoo.h"
 16 
 17 int
 18 main(int argc, char *argv[])
 19 {
 20         int port = PORT;
 21         int rc = 0;
 22 
 23         if (argc != 2) {
 24                 fprintf(stderr, "Usage: %s <ipv4>\n", argv[0]);
 25                 return -1;
 26         }
 27         char *ipv4 = argv[1];
 28 
 29         /* 1. socket */
 30         int listen_fd = socket(PF_INET, SOCK_STREAM, 0);
 31         if (listen_fd < 0) {
 32                 fprintf(stderr, "E: socket(),%d,%s\n",
 33                     errno, strerror(errno));
 34                 return -1;
 35         }
 36 
 37         /* 2. bind */
 38         struct sockaddr_in srv_addr;
 39         memset(&srv_addr, 0, sizeof (struct sockaddr_in));
 40         srv_addr.sin_family = AF_INET;
 41 
 42         rc = inet_pton(AF_INET, ipv4, &srv_addr.sin_addr);
 43         if (rc != 1) {
 44                 fprintf(stderr, "E: inet_pton(),%d,%s\n",
 45                     errno, strerror(errno));
 46                 return -1;
 47         }
 48         srv_addr.sin_port = htons(port);
 49 
 50         rc = bind(listen_fd, (struct sockaddr *)&srv_addr, sizeof (srv_addr));
 51         if (rc != 0) {
 52                 fprintf(stderr, "E: bind(),%d,%s\n",
 53                     errno, strerror(errno));
 54                 return -1;
 55         }
 56 
 57         /* 3. lisen */
 58         rc = listen(listen_fd, 10);
 59         if (rc != 0) {
 60                 fprintf(stderr, "E: listen(),%d,%s\n",
 61                     errno, strerror(errno));
 62                 return -1;
 63         }
 64 
 65         printf("Now tcp server is listening on %s:%d\n", ipv4, port);
 66 
 67         while (1) {
 68                 /* 4. accept */
 69                 struct sockaddr_in clnt_addr;
 70                 size_t clnt_addr_len = sizeof (struct sockaddr_in);
 71                 memset(&clnt_addr, 0, sizeof (struct sockaddr_in));
 72 
 73                 int conn_fd = accept(listen_fd,
 74                                      (struct sockaddr *)&clnt_addr,
 75                                      &clnt_addr_len);
 76                 if (conn_fd < 0) {
 77                         fprintf(stderr, "E: accept(),%d,%s\n",
 78                             errno, strerror(errno));
 79                         return 1;
 80                 }
 81 
 82                 /* get IPv4/port of the server */
 83                 memset(&srv_addr, 0, sizeof (struct sockaddr_in));
 84                 size_t srv_addr_len = sizeof (struct sockaddr_in);
 85                 rc = getsockname(conn_fd,
 86                                  (struct sockaddr *)&srv_addr,
 87                                  &srv_addr_len);
 88                 if (rc != 0) {
 89                         fprintf(stderr, "E: getsockname(),%d,%s\n",
 90                             errno, strerror(errno));
 91                         return 1;
 92                 }
 93 
 94                 char srvipv4[16] = { 0 };
 95                 const char *ptr1 = inet_ntop(AF_INET,
 96                                             &srv_addr.sin_addr,
 97                                             srvipv4,
 98                                             sizeof (srvipv4));
 99 
100                 /* get IPv4/port of the client */
101                 rc = getpeername(conn_fd,
102                                  (struct sockaddr *)&clnt_addr,
103                                  &clnt_addr_len);
104                 if (rc != 0) {
105                         fprintf(stderr, "E: getpeername(),%d,%s\n",
106                             errno, strerror(errno));
107                         return 1;
108                 }
109 
110                 char clntipv4[16] = { 0 };
111                 const char *ptr2 = inet_ntop(AF_INET,
112                                             &clnt_addr.sin_addr,
113                                             clntipv4,
114                                             sizeof (clntipv4));
115 
116                 printf("\nlocal %s port %d connected with %s port %d\n",
117                        ptr1, (int)ntohs(srv_addr.sin_port),
118                        ptr2, (int)ntohs(clnt_addr.sin_port));
119 
120                 /* 5. recv */
121                 rc = recv_file(conn_fd);
122                 if (rc != 0) {
123                         fprintf(stderr, "fail to recv file on fd %d\n",
124                             conn_fd);
125                         close(conn_fd);
126                         continue;
127                 }
128 
129                 close(conn_fd);
130         }
131 
132         /* 6. shutdown */
133         close(listen_fd);
134 
135         return 0;
136 }

4. client.c

 1 /**
 2  * client.c - tcp client to send a file like scp
 3  */
 4 
 5 #include <stdio.h>
 6 #include <unistd.h>
 7 #include <errno.h>
 8 #include <string.h>
 9 #include <fcntl.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <sys/socket.h>
13 #include <arpa/inet.h>
14 #include "libfoo.h"
15 
16 int
17 main(int argc, char *argv[])
18 {
19         int port = PORT;
20         int rc = 0;
21 
22         if (argc != 4) {
23                 fprintf(stderr, "Usage %s <server> <srcfile> <dstfile>\n",
24                     argv[0]);
25                 return -1;
26         }
27 
28         /* 1. socket */
29         int sock_fd = socket(PF_INET, SOCK_STREAM, 0);
30         if (sock_fd < 0) {
31                 fprintf(stderr, "E: socket(),%d,%s\n",
32                     errno, strerror(errno));
33                 return -1;
34         }
35 
36         char *ipv4 = argv[1];
37         char *srcfile = argv[2];
38         char *dstfile = argv[3];
39 
40         /* 2. connect */
41         struct sockaddr_in srv_addr;
42         memset(&srv_addr, 0, sizeof (struct sockaddr_in));
43         srv_addr.sin_family = AF_INET;
44 
45         rc = inet_pton(AF_INET, ipv4, &srv_addr.sin_addr);
46         if (rc != 1) {
47                 fprintf(stderr, "E: inet_pton(),%d,%s\n",
48                     errno, strerror(errno));
49                 return -1;
50         }
51         srv_addr.sin_port = htons(port);
52 
53         rc = connect(sock_fd, (struct sockaddr *)&srv_addr, sizeof (srv_addr));
54         if (rc != 0) {
55                 fprintf(stderr, "E: bind(),%d,%s\n",
56                     errno, strerror(errno));
57                 return -1;
58         }
59 
60         /* 3. send */
61         rc = send_file(sock_fd, dstfile, srcfile);
62         if (rc != 0) {
63                 fprintf(stderr, "fail to send srcfile %s to dstfile %s\n",
64                     srcfile, dstfile);
65                 close(sock_fd);
66                 return 3;
67         }
68 
69         printf("OKAY - send file %s to %s:%s successfully!\n",
70             srcfile, ipv4, dstfile);
71 
72         /* 4. shutdown */
73         close(sock_fd);
74 
75         return 0;
76 }

5. Makefile

 1 CC      = gcc
 2 CFLAGS  = -g -Wall -std=gnu99 -m32
 3 
 4 all: client server
 5 
 6 client: client.o libfoo.o
 7         ${CC} ${CFLAGS} -o $@ $^
 8 
 9 client.o: client.c
10         ${CC} ${CFLAGS} -c $<
11 
12 server: server.o libfoo.o
13         ${CC} ${CFLAGS} -o $@ $^
14 
15 server.o: server.c
16         ${CC} ${CFLAGS} -c $<
17 
18 libfoo.o: libfoo.c libfoo.h
19         ${CC} ${CFLAGS} -c $<
20 
21 clean:
22         rm -f *.o
23 clobber: clean
24         rm -f client server
25 cl: clobber

6. 编译并测试

$ make
gcc -g -Wall -std=gnu99 -m32 -c client.c
gcc -g -Wall -std=gnu99 -m32 -c libfoo.c
gcc -g -Wall -std=gnu99 -m32 -o client client.o libfoo.o
gcc -g -Wall -std=gnu99 -m32 -c server.c
gcc -g -Wall -std=gnu99 -m32 -o server server.o libfoo.o

# --- Terminal 1: start server -------------------------------------------

$ ifconfig eth0 | grep 'inet addr'
          inet addr:192.168.1.100  Bcast:192.168.1.255  Mask:255.255.255.0
$ ./server 192.168.1.100
Now tcp server is listening on 192.168.1.100:2345

# --- Terminal 2: start client -------------------------------------------

$ rm -f /tmp/foo.c
$ ./client 192.168.1.100 /tmp/client.c /tmp/foo.c
OKAY - send file /tmp/client.c to 192.168.1.100:/tmp/foo.c successfully!

$ diff /tmp/client.c /tmp/foo.c
$ echo $?

# --- Back to Terminal 1 -------------------------------------------------

$ ./server 192.168.1.100
Now tcp server is listening on 192.168.1.100:2345

local 192.168.1.100 port 2345 connected with 192.168.1.100 port 60202
> dst filename=/tmp/foo.c, filesize=1532, filemode=644
^C

补充说明:

在一个TCP连接建立之后,我们可以通过socket描述符来获取本地的IP/port和连接对端的IP/port。

  • getsockname(): 用于获取与某个socket关联的本地协议地址
  • getpeername(): 用于获取与某个socket关联的外地协议地址
#include <sys/socket.h>
/* getsockname - get socket name */
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

/* getpeername - get name of connected peer socket */
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 在TCP客户端,如果没有调用bind函数,可以通过调用getsockname()函数获取由内核赋予该连接的本地IP地址和本地端口号;
  • 在TCP服务器端,一旦成功调用accept函数后,可以通过getpeername()函数获取当前连接的客户端的IP地址和端口号。

 

posted @ 2017-05-28 21:28  veli  阅读(3929)  评论(0编辑  收藏  举报