欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux下TCP、UDP网络编程框架

程序员文章站 2022-06-30 10:19:04
...

TCP网络编程框架

3、字节序转换函数。

#include <arpa/inet.h>
uint32_t  htonl(uint32_t hostlong);        /*主机字节序到网络字节序的长整型转换*/
uint16_t  htons(uint16_t hostshort); /*主机字节序到网络字节序的短整型转换*/
uint32_t  ntohl(uint32_t netlong);           /*网络字节序到主机字节序的长整型转换*/
uint16_t  ntohs(uint16_t netshort);         /*网络字节序到主机字节序的短整型转换*/

二、Socket套接字

1、Liunx中的网络编程通过Socket(套接字)实现,Socket是一种文件描述符。

2、socket有三种类型:

流式套接字(SOCK_STREAM):TCP协议。

数据报套接字(SOCK_DGRAM):UDP协议。

原始套接字(SOCK_RAW):允许使用IP协议,用于新的网络协议的测试等。

3、套接字的地址结构:

a、 以太网:互联网就是把多个同种类或者不同种类的小网络用路由器连起来。以太网是最为常用的小网络,或者说局域网。
进行套接字编程需要指定套接字的地址作为参数,不同的协议族有不同的地址结构定义方式。
这些地址结构通常以sockaddr_开头,每一个协议族有一个唯一的后缀,以太网协议族的地址结构为sockaddr_in。

b、Socket编程常用的地址结构

   在socket程序设计中,struct sockaddr_in用于保存socket地址信息:
#include <netinet/in.h> 

struct sockaddr_in

{  
     short int  sin_family;                   /* Internet地址族 */
     unsigned short int  sin_port;      /* 端口号,网络字节序 */  
     struct in_addr  sin_addr;            /* IP地址,网络字节序 */
     unsigned char  sin_zero[8];       /* 0 以保持与 struct sockaddr 同样大小*/   
};

其中:

struct in_addr   //网络IP的类型
{ 
     unsigned long s_addr;
};

三、字符串IP地址和二进制IP地址的转换

1、inet_aton()函数和inet_ntoa()函数

   #include<netddb.h>

 (1) int inet_aton(const char *cp,struct in_addr *inp) //点分十进制转化为二进制IP,结                            果在struct in_addr *中,转化失败返回0
 (2)char *inet_ntoa(struct in_addr in) //二进制转化为点分十进制,结果为char *返回    

2.inet_pton()函数和inet_ntop()函数

   #include <arpa/inet.h> 
 (1)int inet_pton(int af, const char *src, void *dst);   //[将"点分十进制" -> "整数"] 

这个函数转换字符串到网络地址,第一个参数af是地址族(ipv4为AF_INET),转换后存在dst指向的struct in_addr结构体中。
(2)const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); //将"整数" -> “点分十进制”
这个函数转换网络二进制结构到点分十进制的地址,参数的作用和上面相同,只是多了一个参数socklen_t cnt,他是所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。

四、IP和主机名之间的转换

在网络上标识一台机器可以用IP,也可以使用主机名。

#include <netdb.h>
struct hostent *gethostbyname(const char *hostname)
函数说明:实现主机名(网址(需联网)等)到IP的转换

函数返回值:

struct hostent
{
     char *h_name;     /* 主机的正式名称*/   
      char **h_aliases;  /* 主机的别名列表,以NULL结尾*/
            int   h_addrtype;   /* 主机的地址类型  AF_INET(ipv4)*/
      int   h_length;       /* 主机的ip地址长度,ipv4为4*/
      char **h_addr_list;/*主机的IP号的地址列表(为32位网络地址,而不是字符串),以NULL结尾*/

}

五、TCP网络编程架构

TCP网络编程有两种模式,一种是服务器模式,另一种是客户端模式。
服务器模式创建一个服务程序,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求进行处理;
客户端模式则根据目的服务器的地址和端口进行连接,向服务器发送请求并对服务器的响应进行数据处理。

Linux下TCP、UDP网络编程框架

1、套接字初始化(socket())

socket()函数介绍

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);  //如果函数调用成功,会返回一个表示这个 套接字的文件描述符,失败的时候返回–1。

参数:
a.domain即协议域,又称为协议族(family),AF_INET
b.type指定socket类型。常用的socket类型有,SOCK_STREAM、                     SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
c.protocol:故名思意,就是指定协议,一般设置为0

2、套接字与端口的绑定(bind())

(1)、在服务器设计中,建立套接字文件描述符成功后,需要对套接字进行地址和端口的绑定,才能进行数据的接收和发送操作。

(2)、bind()函数

#include <sys/types.h>
 #include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
//将长度为addrlen的struct sockaddr类型的参数my_addr与sockfd绑定在一起,将so

cket绑定到某个端口上

3、设置服务器的侦听连接(listen())

(1)、函数listen()用来初始化服务器可连接队列。
服务器处理客户端连接请求的时候是顺序处理的,同一时间仅能处理一个客户端连接。当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度由listen()函数来定义。

(2)、listen()函数

listen()函数的原型如下,其中的backlog表示等待队列的长度。

 #include <sys/socket.h>
 int listen(int sockfd, int backlog);  //返回值为-1时,说明监听失败

4、接受客户端连接(accept())//连接服务器(connect())

(1)、当一个客户端的连接请求到达服务器主机侦听的端口时,此时客户端的连接会在队列中等待,直到使用服务器处理接收请求。
函数accept()成功执行后,会返回一个新的套接字文件描述符来表示客户端的连接,客户端连接的信息可以通过这个新描述符来获得。

(2)、accept()

 #include <sys/types.h>
 #include <sys/socket.h>
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

sockfd为服务器的socket描述字
addr为指向struct sockaddr *的指针,用于返回客户端的协议地址
addrlen为指向协议地址长度指针。
结果:

如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接;否则返回-1;

   注意:

1.accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字,一个服务器通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。
2.而accept函数返回的是已连接的socket描述字。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

(3)、客户端在建立套接字之后,不需要进行地址绑定就可以直接连接服务器。连接服务器的函数为connect(),此函数连接指定参数的服务器,例如IP地址、端口等。

(4)、connect()

connect()函数的原型如下。

 #include <sys/types.h>
 #include <sys/socket.h>
 int connect(int sockfd, struct sockaddr* addr, int addrlen); 

5、接受和发送数据(read()、write())

(1)、当服务器端在接收到一个客户端的连接后,可以通过套接字描述符进行数据的写入操作。对套接字进行写入的形式和过程与普通文件的操作方式一致,内核会根据文件描述符的值来查找所对应的属性,当为套接字的时候,会调用相对应的内核函数。

(2)、write()

 int size ;
char data[1024];
  size = write(sockfd, data, 1024);

(3)、使用read()函数可以从套接字描述符中读取数据。在读取数据之前,必须建立套接字并连接。

(4)、read()

int size ; 
  char data[1024];
  size = read(s, data, 1024);

6、数据处理及套接字关闭(close())

   close(int sockfd);

下面是具体实现代码:
服务端代码:sy2.c

#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <strings.h>

main()
{
    pid_t pid;
    int socket_fd,accept_fd; //socket套接字文件描述符
    int addrlen,iBytes;
    unsigned char buf[256];
    struct sockaddr_in server,client; //用于保存socket地址信息

/*
服务端的流程:socket()--->bind()--->listen()--->accept()--->read()--->write()--->close()
*/

/****************socket()*****************/
/* 服务器端开始建立socket_fd描述符*/ 
//套接字初始化(socket()),如果函数调用成功,会返回一个表示这个 套接字的文件描述符,失败的时候返回–1。
   if((socket_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
     perror("Error--socket:");
      _exit(-1);
   }

/* 服务器端填充 sockaddr结构*/
   bzero(&server,sizeof(server)); //给server分配空间初始化
   server.sin_family = AF_INET; // Internet地址族
   server.sin_port = htons(4000); //端口号,网络字节序
   server.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,网络字节序;  (将本机器上的long数据转化为网络上的long数据)服务器程序能运行在任何ip的主机上  //INADDR_ANY 表示主机可以是任意IP地址,即服务器程序可以绑定到所有的IP上


/****************bind()*****************/
   //在服务器设计中,建立套接字文件描述符成功后,需要对套接字进行地址和端口的绑>定,才能进行数据的接收和发送操作.
   //将长度为addrlen的struct sockaddr类型的参数server与socket_fd绑定在一起,将socket绑定到某个端口上
   if(bind(socket_fd,(struct sockaddr *)&server,sizeof(server)) == -1 ) {
      perror("Error-bind:");
      _exit(-2);
   }

/****************listen()*****************/
//函数listen()用来初始化服务器可连接队列,其中的5表示等待队列的长度。
   if(listen(socket_fd,5) == -1) {
     perror("Error-listen:");
      _exit(-3);
    }

/****************accept()*****************/
    addrlen = sizeof(client);
    for(;;) {
    //1.accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生
成的,称为监听socket描述字,一个服务>器通常仅仅只创建一个监听socket描述字,它在>该服务器的生命周期内一直存在。
 // 2.而accept函数返回的是已连接的socket描述字。内核为每个由服务器进程接受的客户
连接创建了一个已连接socket描述字,当>服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
      if((accept_fd = accept(socket_fd,(struct sockaddr *)&client,&addrlen)) == -1)
       {
          perror("Error-accept:");
          _exit(-4);
       }
       pid = fork(); //如果监听成功就创建子进程
       if(pid > 0) continue; //父进程继续监听
       else if(pid == -1) {
          perror("Error-fokr");
          _exit(-5);
       }

/****************read()*****************/
       //以下都为子进程的动作
       bzero(buf,256);
       iBytes = read(accept_fd,buf,256); //从客户端读取接收到的信息
       if(iBytes == -1) {
          perror("Error-recv");
          _exit(-6);
       }

/****************write()*****************/
       //inet_ntoa()函数:二进制转化为点分十进制,结果为char *返回值
       //ntohs(uint16_t netshort);         /*网络字节序到主机字节序的短整型转换*/
       printf("[ %s:%d]发来连接请求:%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
        if(write(accept_fd,"Welcome.baby!\n",15) == -1) { //给客户端发出回应信息
           perror("Error-send");
              _exit(-7);
          }

/****************close()*****************/
         close(accept_fd);
         _exit(0);
    }
}

客户端代码:sy3.c

#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <strings.h>

main()
{
    int socket_fd;  //socket套接字文件描述符
    int addrlen,iBytes;
    unsigned char buf[256];
    struct sockaddr_in server;  // 用于保存socket地址信息

/*
客户端的流程:socket()--->connect()--->write()--->read()--->close()
*/

/****************socket()*****************/
/* 客户程序开始建立 socket_fd描述符*/
  if((socket_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
     perror("Error--socket:");
      _exit(-1);
   }

   bzero(&server,sizeof(server)); //给server分配空间初始化
   server.sin_family = AF_INET; // Internet地址族
   server.sin_port = htons(4000); //端口号,网络字节序
   server.sin_addr.s_addr = inet_addr("127.0.0.1"); //IP地址,网络字节序

/****************connect()*****************/
/* 客户程序发起连接请求 */
//连接服务器的函数为connect(),此函数连接指定参数的服务器,例如IP地址、端口等 
   if(connect(socket_fd,(struct sockaddr *)&server,sizeof(server)) == -1) {
       perror("Error--connect:");
       _exit(-2);
   }

/****************write()*****************/
   if(write(socket_fd,"嗨,我是广州!\n",17) == -1) { //给服务端发出请求信息
          perror("Error-write");
          _exit(-3);
   }
/****************read()*****************/
       bzero(buf,256);
       iBytes = read(socket_fd,buf,256); //读取服务端的响应信息
       if(iBytes == -1) {
          perror("Error-read");
          _exit(-4);
       }
      printf("服务器的响应:%s\n",buf);

/****************close()*****************/
       close(socket_fd);
        _exit(0);
}

Linux下TCP、UDP网络编程框架

服务器端整个程序的工作过程:socket()—>bind()—>listen()—>accept()—>read()—>write()—>close()
1.首先服务器端创建socket,以及服务器和客户端的地址信息初始化;
2.再通过bind函数把socket和服务端地址绑定;
3.然后服务器开始使用listen函数进行监听;
4.监听成功后,使用accept函数接受监听内容;
5.创建子进程读取消息内容(read函数),父进程继续监听listen函数;
6.服务器对收到的消息进行处理,用write函数给客户端发出一个回应消息;
7.关闭socket

客户端整个工作过程:socket()—>connect()—>write()—>read()—>close()

1.首先创建socket,以及服务器地址信息初始化;
2.connect()函数与服务器进行TCP三次握手连接;
3.用write()函数给服务器发送消息;
4.接收服务器端的回应消息read();
5.关闭socket,close()。

参考文献链接: Linux网络编程:TCP网络编程构架.



UDP网络编程框架

  1. UDP编程框架
    要使用UDP协议进行程序开发,我们必须首先得理解什么是什么是UDP?这里简单概括一下。

UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层。UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对方。所以UDP适用于一次传输数据量很少、对可靠性要求不高的或对实时性要求高的应用场景。正因为UDP无需建立类如三次握手的连接,而使得通信效率很高。

UDP的应用非常广泛,比如一些知名的应用层协议(SNMP、DNS)都是基于UDP的,想一想,如果SNMP使用的是TCP的话,每次查询请求都得进行三次握手,这个花费的时间估计是使用者不能忍受的,因为这会产生明显的卡顿。所以UDP就是SNMP的一个很好的选择了,要是查询过程发生丢包错包也没关系的,我们再发起一个查询就好了,因为丢包的情况不多,这样总比每次查询都卡顿一下更容易让人接受吧。

UDP通信的流程比较简单,因此要搭建这么一个常用的UDP通信框架也是比较简单的。以下是UDP的框架图。
Linux下TCP、UDP网络编程框架

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);

第一个参数sockfd:正在监听端口的套接口文件描述符,通过socket获得
第二个参数buf:发送缓冲区,往往是使用者定义的数组,该数组装有要发送的数据
第三个参数len:发送缓冲区的大小,单位是字节
第四个参数flags:填0即可
第五个参数dest_addr:指向接收数据的主机地址信息的结构体,也就是该参数指定数据要发送到哪个主机哪个进程
第六个参数addrlen:表示第五个参数所指向内容的长度
返回值:成功:返回发送成功的数据长度
失败: -1

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);

第一个参数sockfd:正在监听端口的套接口文件描述符,通过socket获得
第二个参数buf:接收缓冲区,往往是使用者定义的数组,该数组装有接收到的数据
第三个参数len:接收缓冲区的大小,单位是字节
第四个参数flags:填0即可
第五个参数src_addr:指向发送数据的主机地址信息的结构体,也就是我们可以从该参数获取到数据是谁发出的
第六个参数addrlen:表示第五个参数所指向内容的长度
返回值:成功:返回接收成功的数据长度
失败: -1

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);

第一个参数sockfd:正在监听端口的套接口文件描述符,通过socket获得
第二个参数my_addr:需要绑定的IP和端口
第三个参数addrlen:my_addr的结构体的大小
返回值:成功:0
失败:-1

#include <unistd.h>
int close(int fd);

close函数比较简单,只要填入socket产生的fd即可。

下面是具体实现代码:
服务端代码:sy4.c

#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
    int socket_fd; //socket套接字文件描述符
    int recv_num;
    int send_num;
    int client_len;
    char recv_buf[20];
    unsigned char buf[256];
    struct sockaddr_in addr_serv,addr_client; //用于保存服务器和客户端地址信息

/*
服务端的流程:socket()--->bind()--->recvfrom()--->sendto()--->close()
*/
/****************socket()*****************/
/* 开始建立 socket_fd描述符*/
   if((socket_fd = socket(AF_INET,SOCK_DGRAM,0))  < 0) {
     perror("Error--socket:");
      exit(1);
   }

  //初始化服务器端地址
   bzero(&addr_serv,sizeof(addr_serv)); //给server分配空间初始化
   addr_serv.sin_family = AF_INET; // Internet地址族
   addr_serv.sin_port = htons(4001); //端口号,网络字节序
   addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,网络字节序

  client_len = sizeof(struct sockaddr_in);
  //绑定套接字与地址
   /****************bind()*****************/

  if(bind(socket_fd,(struct sockaddr *)&addr_serv,sizeof(struct sockaddr_in)) < 0 ) {
      perror("Error-bind:");
      exit(1);
   }

  while(1)
  {
   /****************recvfrom()*****************/
   //从客户端接收数据
    recv_num = recvfrom(socket_fd,recv_buf,50,0,(struct sockaddr *)&addr_client,&client_len);
    if(recv_num < 0)
    {
       perror("again recvfrom");
       exit(1);
    }else {
	printf("%d\n",recv_num);
       recv_buf[recv_num] = '\0';
       printf("recv sucess:%s\n",recv_buf);
    }
  /****************sendto()*****************/
  //给客户端发送数据
   //printf("server:Hello,World!\n");
     send_num = sendto(socket_fd,"Hello!我是服务器!收到啦",50,0,(struct sockaddr *)&addr_client,client_len); 
   if(send_num < 0) 
   {
      perror("sendto");
      exit(1);
   }else {
      //printf("send sucess:%s\n",recv_buf);
   }
 }
  /****************close()*****************/
  close(socket_fd);
  return 0;
}

客户端代码:sy5.c

#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <strings.h>

int main()
{
   int recv_num;
   char recv_buf[512];
/*
客户端的流程:socket()--->sendto()--->recvfrom()--->close()
*/
   struct sockaddr_in addr_serv;
   bzero(&addr_serv,sizeof(addr_serv)); //给server分配空间初始化
   addr_serv.sin_family = AF_INET; // Internet地址族
   addr_serv.sin_port = htons(4001); //端口号,网络字节序
   addr_serv.sin_addr.s_addr = inet_addr("192.168.43.130"); //IP地址,网络字节序


/****************socket()*****************/
  int socket_fd;
  if((socket_fd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
     perror("Error--socket:");
      exit(1);
   }
   
  
/****************sendto()*****************/
//给服务器发送信息,其中(struct sockaddr *)&addr_serv是目的地址,sizeof(addr_serv)是目的地址大小
	if(sendto(socket_fd,"Hello,我是客户端!\n",50,0,(struct sockaddr *)&addr_serv,sizeof(addr_serv)) < 0) { //给服务端发出请求信息
          perror("Error-sendto");
          exit(1);
    	}

/****************recvfrom()()*****************/
//接收来自服务器端的数据,其中,(struct sockaddr *)&addr_serv是发送方的地址,即服务器地址
        int server_len = sizeof(struct sockaddr_in);      
       //recv_num = recvfrom(socket_fd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)&addr_serv, &server_len);
	//recv_num = recvfrom(socket_fd,recv_buf,sizeof(recv_buf),0,NULL, NULL);
	
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//这里出错了,找了老半天。因为括号没打,所以导致recv_num=0,应该要把整个运算括起来,再判断是否 < 0才正确
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	if( (recv_num = recvfrom(socket_fd,recv_buf,sizeof(recv_buf),0,NULL, NULL) ) < 0)
       {
         perror("again recvfrom");
         exit(1);
       }
	
       printf("%d\n",recv_num);
       printf("recv sucess:%s\n",recv_buf);


      

/****************close()*****************/
       close(socket_fd);
       return 0;
}

Linux下TCP、UDP网络编程框架

服务器端整个程序的工作过程:socket()—>bind()—>recvfrom()—>sendto()—>close()

1.首先服务器端创建socket,以及服务器和客户端的地址信息初始化;
2.再通过bind函数把socket和服务端地址绑定;
3.服务器接收客户端发送的消息==>recvfrom()函数
3.服务器对收到的消息进行处理,用sendto()函数给客户端发出一个回应消息;
7.关闭socket,close()。

客户端整个工作过程:socket()—>sendto()—>recvfrom()—>close()

1.首先创建socket,以及服务器地址信息初始化;
2.用sendto()函数给服务器发出一个请求消息;
3.用recvfrom()函数给接收服务器端的响应消息;
4.关闭socket,close()。

参考文献链接: Linux编程之UDP SOCKET全攻略.

相关标签: 杂七杂八