网络通讯TCP和UDP的编程流程

        我们介绍过进程间通讯的方式有:管道、信号量、共享内存、消息队列,但这些通讯方式都是在本地上通讯的,那么在现实生活中还有一种通信方式,不同主机上的两个进程之间的通讯,是通过socket套接字来完成的。

        比如,生活中聊天软件QQ,你和好友之间的聊天,就是网络通讯。你是客户端,好友也是客户端,那么你们之间能够通讯是因为中间还有个服务器在工作。

    服务器:提供数据的为服务器

    客户端:获取数据的为客户端


       在计算机中要做到有条不紊的交换数据,就必须遵守一些实现约定好的规则。这些规则明确规定了所交换的数据的格式以及有关的同步问题。

      所以我们有了网络协议,网络协议有TCP/IP   UDP/IP

      协议的选择:    TCP 的特点:     面向连接     可靠传输     流式服务

                                UDP的特点:      无连接         不可靠         数据报服务  


这里我们主要说说TCP和UDP的编程流程

TCP的编程流程:

            server(服务器):     socket       bind           listen         accept        recv/send       close

            client (客户端):      socket      /*bind*/         connect              recv/send       close


1、服务器端:

         (1)创建socket

              socket ,在linux下一切都是文件,socket就是可读、可写、可控制、可关闭的文件描述符。

                #include<sys/types.h>

                #include<sys/socket.h>

                int socket(int domain,    int type,     int protocol)

               domain 参数:系统使用的是哪个底层协议族。TCP/IP协议族,参数设置为PF_INET

                                                                                   UNIX本地域   ,参数设置为PF_UNIX


               type参数:指定服务类型  ,服务类型就是流服务或者数据报服务,TCP协议使用流服务SOCK_STREAM,

                              UDP使用数据报服务SOCK_DGRAM.

               protocol参数:是在前面爱那个参数构成的协议下,再选择一个具体的协议。我们一般设置为0,表示使用默  认协议。

                函数成功返回一个socket文件描述符,失败返回-1.

       

(2)bind (绑定)

             创建socket时,我们给它指定了地址族,但未指定使用该地址族中的哪个具体socket地址。

             将一个socket 与socket地址绑定称为给socket命名,在服务器程序中我们需要命名socket,因为只有命名后服务器才能知道该如何连接它。客户端通常不需要命名,采用匿名方式,使用操作系统自动分配的socket地址。

              #include<sys/types.h>

             #include<sys/socket.h>

             int bind (int sockfd,  const struct sockaddr* my_addr,   int  addrlen)

              sockfd就是socket函数的返回值;

              参数2:  sockaddr:指定IP地址和端口号

                              它是个结构体:    struct  sockaddr_in

                                                              {

                                                                        sa_family_t     sin_family;//地址族,AF_INET

                                                                        u_int16_t         sin_port;//端口号,要用网络字节序表示

                                                                         struct   in_addr   sin_addr;//IPV4  地址结构体

                                 }

                               struct in_addr

                               {

                                            u_int32_t    s_addr;}//IPV4地址,要用网络字节序来表示


          参数3:addlen指出该socket地址的长度,也就是struct sockaddr_in结构体的长度。

          成功返回0,失败返回-1,并设置errno。

 

(3)listen(监听)

         socket被命名之后,还不能马上被接受客户连接,我们需要使用如下系统调用来创建一个监听队列以存放待处理的客户连接。

         #include<sys/socket.h>

         int listen(int socket   ,int   size);

        sockfd就是socket函数的返回值

        size 参数提示内核监听队列的最大长度,如果监听队列的长度超过size,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息

        成功返回0,失败返回-1,并设置errno。


(4)accept(接受连接)

         系统调用从listen队列中接受一个连接:

         #include<sys/types.h>

        #include<sys/socket.h>

        int accept(int sockfd,  struct  sockaddr *addr,   int   *addrlen)

         第一参数句不多说了;
        addr参数是用来获取被接受连接的远端socket 地址(我们这里接受连接的是服务器端,那么被接收连接的就是客户端client),
       addrlen  表示该地址的长度(这里是客户端的地址)
       accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通讯。
        accept失败时返回-1,并设置errno。

(5)recv/send(获取信息/发送信息)

       accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通讯。

         recv( socket,void *buff,int size,int flag)

         send(socket,void* buff,int size)

(6)  close(关闭连接)

      #include<unistd.h>

      int close(int   fd);

       fd参数是待关闭的socket,不过,close系统调用并非总是立即关闭一个连接,而是将  fd的引用计数减一,只有当fd的引用计数为0时,才真正关闭连接。

      如果无论如何都要立即终止连接(而不是将fd的引用计数减一),可以使用shutdown系统调用

            #include<sys/socket.h>

             int   shutdown(int  sockfd,int   howto)

              howto参数决定了shutdown的行为:  1、SHUT_RD     关闭读

                                                                          2、SHUT_WR    关闭写

                                                                          3、SHUT_RDWR      同时关闭读和写

2、客户端

       socket       /*bind*/       connect        recv/send          close

    发起请求的是客户端,那么客户端是不需要监听来创建一个等待处理连接的队列的,也没有accept了。

    所以  1、创建socket与前面服务器的是一样的。

              2、客户端通常不需要命名,采用匿名方式,使用操作系统自动分配的socket地址。所以bind我们可以添加,也可以不添加。

              3、connect(发起连接)

                   #include<sys/types.h>

                   #include<sys/socket.h>

                   int   connect(int sockfd,   const   sockaddr   *serv_addr,    int   addrlen)

                   serv_addr  是指服务器监听的socket地址,addrlen指定这个地址的长度。

                  connect 成功时返回0,一旦成功连接,sockfd就唯一标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。

                  失败返回-1,并设置errno。

                  两种最常见的errno:

                                              1、ECONNREFUSED,目标端口不存在,连接被拒绝。

                                               2、ETIMEDOUT,连接超时。


               4、recv/send   close 同服务器一样。

二、UDP的编程流程:

     服务器:socket   bind    recvfrom/sendto     close

     客户端:socket         sendto/recvfrom          close

因为UDP的特点是无连接的,所以就没有监听和接受或发起连接的过程了。

前面已经说过socket 和 bind ,那么就不再说了,创建socket的时候协议要变成SOCK_DGRAM;

#include<sys/types.h>
#include<sys/socket.h>
int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,const void *buf,int len,int flags)
recv读取sockfd上的数据。
buf 和 len 参数分别指定读缓冲区的位置和大小,
flags参数通常设置为0,
recv成功时返回实际读到的数据的长度,可能小于我们定义的长度,因此要多次调用recv,才能读到完整数据。
recv返回0,表示通信对方已经关闭连接了。
失败返回-1。

send往sockfd上写入数据,buf和len 参数分别指定缓冲区的位置和大小,
成功返回实际写入数据的长度,失败返回-1.

UDP的编程代码如下:
服务器端User:

#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
void main()
{
	int sockfd=socket(AF_INET,SOCK_DGRAM,0);
        assert(sockfd!=-1);
    
	struct sockaddr_in cli, ser;
        ser.sin_family=AF_INET;
	ser.sin_port=htons(5000);
	ser.sin_addr.s_addr=inet_addr("127.0.0.1");

	int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
        assert(res!=-1);
	while(1)
	{
	    char buff[128]={0};
            int len=sizeof(cli);
	    int recv=recvfrom(sockfd,buff,127,0,(struct sockaddr*)&cli,&len);

	    assert(recv!=-1);
  	    printf("addr::%s,port::%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
	    printf("%s\n",buff);
	
	    sendto(sockfd,"OK",2,0,(struct sockaddr*)&cli,len);

    }
    close(sockfd);
}
UDP编程客户端cli:
#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
void main()
{
	int sockfd=socket(AF_INET,SOCK_DGRAM,0);
        assert(sockfd!=-1);
    
	struct sockaddr_in cli, ser;
        ser.sin_family=AF_INET;
	ser.sin_port=htons(5000);
	ser.sin_addr.s_addr=inet_addr("127.0.0.1");

	char buff[128]={0};
        int len=sizeof(ser);
        sendto(sockfd,"hello world",12,0,(struct sockaddr*)&ser,len);
	int recv=recvfrom(sockfd,buff,127,0,NULL,NULL);

	printf("%s\n",buff);
	
}
运行结果:










        

    





猜你喜欢

转载自blog.csdn.net/lyt15829797751/article/details/78469426