Linux下TCP、UDP网络编程框架

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

在这里插入图片描述

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);
}

在这里插入图片描述

服务器端整个程序的工作过程: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的框架图。
在这里插入图片描述

#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;
}

在这里插入图片描述

服务器端整个程序的工作过程: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全攻略.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值