在网络应用如火如荼的今天,熟悉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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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 ", 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> ", 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 ", 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 ", 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 ", 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 ", 61 errno, strerror(errno)); 62 return -1; 63 } 64 65 printf("Now tcp server is listening on %s:%d ", 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 ", 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 ", 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 ", 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(" local %s port %d connected with %s port %d ", 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 ", 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> ", 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 ", 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 ", 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 ", 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 ", 64 srcfile, dstfile); 65 close(sock_fd); 66 return 3; 67 } 68 69 printf("OKAY - send file %s to %s:%s successfully! ", 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地址和端口号。