TCP编程API

以下为一对 TCP 客户和服务器进程之间发生的一些典型事件的时间表。

 

 

 

服务器首先启动,稍后某个客户启动,它试图连接到服务器。我们假设客户给服务器发送一个请求,服务器处理该请求,并给客户发回一个响应。这个过程持续下去,直至客户关闭连接的客户端,从而给服务器发送一个 EOF (文件结束) 通知为止。服务器接着也关闭连接的服务器端,然后结束运行或等待新的客户连接。
(以上资链接:https://blog.csdn.net/YangLei253/article/details/90748313)

1、socket()函数

  在进行网络I/O时,一个进程最先调用的就是socket()函数,指定期望的通信协议。

1  #include <sys/types.h>          /* See NOTES */
2  #include <sys/socket.h>
3 
4  int socket(int domain, int type, int protocol);

  参数:

  1)domain:协议族/协议域

  AF_UNIX, AF_LOCAL   Local communication       unix(7)
  AF_INET        IPv4 Internet protocols        ip(7)
  AF_INET6           IPv6 Internet protocols        ipv6(7)

  AF_NETLINK                Kernel user interface device     netlink(7)

  AF_PACKET                 Low level packet interface        packet(7)

  2) type:套接字类型

  SOCK_STREAM:流式套接字,唯一对应TCP

  SOCK_DGRAM:数据包套接字,唯一对应UDP

  SOCK_RAW:原始套接字

  3)protocol:一般填0,原始套接字编程时需填充

  返回值:

  成功返回非负整数,即套接字描述符,类似于文件描述符,出错返回-1

2、bind()函数

  在套接口中,一个套接字只是用户程序与内核交互信息的枢纽,它自身没有太多的信息,也没有网络协议地址和端口号等信息,在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程。许多时候内核会我们自动绑定一个地址,然而有时用户可能需要自己来完成这个绑定的过程,以满足实际应用的需要,最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口以等待客户来连接。这个事由bind的函数完成。

1 #include <sys/types.h>          /* See NOTES */
2 #include <sys/socket.h>
3  //bind将一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是 32位 IPv4 地址或 128 位 IPv6 地址与 16 位的 TCP 或 UDP 端口号组合。
4 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  sockfd   :待连接的sockfd,通过socket获得
  addr      :本地的通用套接字地址结构
  addrlen :地址长度

  sockaddr 与 sockaddr_in(详见)

 1)sockaddr

  struct sockaddr 是一个通用地址结构,这是为了统一地址结构的表示方法,统一接口函数,使不同的地址结构可以被bind() , connect() 等函数调用;但缺陷在于sa_data把目标地址和端口信息混在一起

1 struct sockaddr {  
2      unsigned short sa_family;  //地址族
3    char sa_data[14];  //14字节,包含套接字中的目标地址与端口信息                
4    }; 

  2)sockaddr_in

   struct sockaddr_in中的in 表示internet,即网络地址,为比较常用的地址结构,属于AF_INET地址族,
        sockaddr_in结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中

 1 struct sockaddr_in { 
 2     short int          sin_family;  //地址族 NBD
 3     unsigned short int sin_port;    //16位TCP/UDP端口号
 4      struct in_addr     sin_addr;     //32位IP地址
 5      unsigned char      sin_zero[8]; //8字节填充,不使用
 6  }       
 7 
 8 struct in_addr {           
 9      unsigned long s_addr;   //32位IPV4地址
10           }       
11 sin_zero 初始值应该使用函数 bzero() 来全部置零。

  sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

  sockaddr_in结构体变量的基本配置

 

struct sockaddr_in ina;

bzero(&ina,sizeof(ina));

ina.sin_family=AF_INET;

ina.sin_port=htons(23);
ina.sin_addr.s_addr = inet_addr("132.241.5.10");  

  3)两者相互关系

 sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。 
 sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以。     一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值sockaddr用于函数参数

 

3、listen()函数

  设置监听模式与监听上限

  listen 函数仅由 TCP 服务器调用,当 socket 函数创建一个套接字时,默认为一个主动套接字,也就是说它是一个将调用 connect 发起连接的客户套接字。

  listen 函数把一个未连接的套接字转换为一个被动套接字,指示内核应该接收指向该套接字的连接请求,同时·设置accept监听数量的上限。

1 #include<sys/socket.h>
2 #include<sys/types.h>
3 int listen(int sockfd,int backlog);
4 //第二个参数规定内核应该为相应套接字排队的最大连接个数

  参数:

  sockfd:通过socket函数拿到的fd

  backlog:同时允许几路客户端和服务器进行正在连接的过程(正在三次握手)

           一般填5,测试可知,ARM最大为8

注:内核中服务器的套接字fd会维护2个链表:

  1、正在三次握手的客户端链表(数量 = 2*backlog + 1)

  2、已经建立好连接的客户端链表(已经完成三次握手,分配好了newfd)

  eg:listen(fd, 5);  //表示系统允许11 = 2*5 + 1个客户端进行三次握手

  返回值:

  成功返回0,出错返回-1

 


4、accept()函数

  阻塞等待客户端连接请求

1 #include <sys/types.h>          /* See NOTES */
2 #include <sys/socket.h>
3 //等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字
4 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  参数:

  sockfd:经过前面socket()创建并通过bind(),listen()设置的fd

  addr和addrlen

  返回值:

  成功返回新建的newfd, 失败返回-1

 

5、客户端的连接函数 connect()

      客户端使用 connect 函数来建立与 TCP 服务器的连接。

1 #include <sys/socket。h>
2 #include <sys/types.h>
3 
4 int connect(int sockfd,const struct sockaddr *servaddr,socklent_t addrlen);

  connect()和服务端的bind()函数写法基本一致。

 

当服务器的监听地址是INADDR_ANY时,意思不是监听所有的客户端IP。而是服务器端的IP地址可以随意配置,这样使得该服务器端程序可以运行在任意计算机上,可使任意计算机作为服务器,便于程序移植。将INADDR_ANY换成127.0.0.1也可以达到同样的目的。这样,当作为服务器的计算机的IP有变动或者网卡数量有增减,服务器端程序都能够正常监听来自客户端的请求。

测试:基于TCP协议的简单客户端与服务器交互

基于TCP(面向连接)的socket编程,分为客户端和服务器端。

客户端的流程如下:

(1)创建套接字(socket)

(2)向服务器发出连接请求(connect)

(3)和服务器端进行通信(send/recv)

(4)关闭套接字

服务器端的流程如下:

(1)创建套接字(socket)

(2)将套接字绑定到一个本地地址和端口上(bind)

(3)将套接字设为监听模式,准备接收客户端请求(listen)

(4)等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)

(5)用返回的套接字和客户端进行通信(send/recv)

(6)返回,等待另一个客户请求。

(7)关闭套接字。

 1 #include "net.h"
 2 
 3 int main()
 4 {
 5     int fd = -1;
 6     struct sockaddr_in sin; //Internet环境下套接字的地址形式,对其进行操作以建立信息
 7     
 8     /*1、创建套接字描述符fd */
 9     if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0)
10     {
11         perror("socket");
12         exit(1);
13     }
14 
15     /*2、将套接字绑定到一个本地地址与端口上*/
16     
17     /*2.1 填充struct sockaddr_in结构体变量*/
18     bzero(&sin, sizeof(sin)); //初始值置零
19     //sin_port和sin_addr都必须是NBD,且可视化的数字一般都是HBD,如端口号23
20     sin.sin_family = AF_INET; //协议,ipv4
21     sin.sin_port   = htons(SERV_PORT); //将端口号转化为NBD
22 
23     /*优化1:让服务器能绑定在任意IP上*/
24     sin.sin_addr.s_addr = htonl(INADDR_ANY);
25 
26     /*2.2 绑定 */
27     if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
28     {
29         perror("bind");
30         exit(1);
31     }
32 
33     /*3. 把套接字设为监听模式,准备接收客户请求*/
34     if(listen(fd, BACKLOG) < 0)
35     {
36         perror("listen");
37         exit(1);
38     }
39     printf("Severing start...OK!\n");
40 
41     /*4.等待客户请求到来,当请求到来后,接收请求,返回一个基于此次的新的套接字 */
42     int newfd = -1;
43 
44     /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址与端口号 */
45     struct sockaddr_in cin;
46     socklen_t addrlen = sizeof(cin);
47     //获取客户端信息
48     if((newfd = accept(fd, (struct sockaddr *)&cin, &addrlen))<0)
49     {
50         perror("accept");
51         exit(1);
52     }
53     //读出客户端信息,并将HBD转为NBD
54     char ipv4_addr[16];
55     if(!inet_ntop(AF_INET, (void *)&cin.sin_addr,ipv4_addr,sizeof(cin)))
56     {
57         perror("inet_ntop");
58         exit(1);
59     }
60     //打印客户端的IP和端口号
61     printf("Client(%s,%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port));
62 
63 
64     /*5. 读写数据*/
65     int ret = -1;
66     char buf[BUFSIZ];
67 
68     while(1)
69     {
70     bzero(buf, BUFSIZ);
71         do{
72             ret = read(newfd, buf, BUFSIZ-1);
73         }while(ret < 0 && EINTR == errno);
74         if(ret < 0)
75         {
76             perror("read");
77             exit(1);
78         }
79 
80         if(!ret) //对方已经关闭
81         {
82             break;
83         }
84         printf("Receive data: %s",buf);
85 
86         if(!strncasecmp(buf,QUIT_STR, strlen(QUIT_STR)))
87         {
88             printf("Client is exiting!\n");
89             break;
90         }
91     }
92     close(newfd);
93     close(fd);
94     return 0;
95 
96 }
server.c
 1 #include "net.h"
 2 
 3 int main()
 4 {
 5     int fd = -1;
 6     struct sockaddr_in sin;
 7 
 8     /*1.创建sock fd */
 9     if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0)
10     {
11         perror("socket");
12         exit(1);
13     }
14 
15     /*2.连接服务器 */
16     /*2.1 填充struct sockaddr_in结构体变量*/
17     bzero(&sin, sizeof(sin)); //初始值置零
18     sin.sin_family = AF_INET; //
19     sin.sin_port = htons(SERV_PORT); //转化为NBD
20 #if 0
21     sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
22 #else
23     if(inet_pton(AF_INET, SERV_IP_ADDR,(void *)&sin.sin_addr.s_addr) != 1)
24     {
25         perror("inet_pton");
26         exit(1);
27     }
28 #endif
29 
30     if(connect(fd,(struct sockaddr *)&sin, sizeof(sin)) < 0)
31     {
32         perror("connect");
33         exit(1);
34     }
35 
36     printf("Client starting ...\n");
37 
38     /*3.读写数据*/
39     char buf[BUFSIZ];
40     int ret = -1;
41     while(1)
42     {
43         bzero(buf,BUFSIZ);
44         if(fgets(buf, BUFSIZ-1, stdin) == NULL)
45         {
46             continue;
47         }
48         do{
49             ret = write(fd, buf, strlen(buf));
50         }while(ret < 0 && EINTR == errno);
51         if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR)))
52         {
53             printf("Clinet is exiting!\n");
54             break;
55         }
56 
57     }
58 
59     /*4.关闭套接字 */
60     close(fd);
61 
62         return 0;
63 }
client.c
 1 #ifndef __MAKEU_NET_H__
 2 #define __MAKEU_NET_H__
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <strings.h>
 6 #include <stdlib.h>
 7 #include <unistd.h>
 8 #include <sys/types.h>
 9 #include <sys/socket.h>
10 #include <errno.h>
11 #include <netinet/in.h>
12 #include <netinet/ip.h>
13 
14 #define BACKLOG 5
15 #define QUIT_STR "quit"
16 #define SERV_PORT 5001
17 #define SERV_IP_ADDR "192.168.31.123"
18 
19 #endif
net.h

测试结果:

posted @ 2020-01-05 16:41  朱果果  阅读(808)  评论(0编辑  收藏  举报