• 图说使用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
    ",
     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地址和端口号。
  • 相关阅读:
    无锁队列以及ABA问题
    bigworld源码分析(3)——dbMgr分析
    bigworld源码分析(4)——BaseAppMgr分析
    bigworld源码分析(5)——BaseApp分析
    bigworld源码分析(2)—— loginApp分析
    bigworld源码分析(1)—— 研究bigworld的意义和目标
    C++嵌入Python,以及两者混用
    B-Tree算法分析与实现
    通过sqlserver日志恢复误删除的数据
    win7启动时怎么自动进入桌面
  • 原文地址:https://www.cnblogs.com/idorax/p/6915141.html
Copyright © 2020-2023  润新知