进程间通信(3)——套接字(TCP和UDP)

常用的进程间通信方式

1.传统的进程间通信方式: 无名管道(pipe)、有名管道(fifo)和信号(signal)

2.System V IPC对象: 共享内存(share memory)、消息队列(message queue)和信号灯(semaphore)

3.BSD :套接字(socket)
一、套接字概念

        与之前介绍的通信方式不同,套接字主要用于网络通信,也就是不同主机上的进程通信。在OSI七层模型或是当前通用的TCP/IP四层模型中。网络层使用的IP协议通常是建立点对点的连接,即主机和主机之间的连接,在传输层中引入端口号,通过套接字(IP+端口号)实现了端到端的连接,也就是主机进程到主机进程之间的连接。是一个编程接口,是一种特殊的文件描述符,并不仅限于TCP/IP协议。

 1.流式套接字(SOCK_STREAM)
        提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
2.数据报套接字(SOCK_DGRAM)
        提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
3.原始套接字(SOCK_RAW)
        可以对较低层次协议如IP、ICMP直接访问。

二、传输层之协议;

        在传输层中最重要的协议便是,面向连接的TCP协议和面向无连接的UDP协议。

1、TCP       

         TCP允许两个应用进程之间建立一条传输连接,实现顺序、无差错、不重复和无报文丢失的可靠传输(三次握手)。
        TCP 是面向字节流的协议。源主机上的应用进程调用 TCP 进程,将用户数据写入到 TCP 发送缓存,TCP 将发送缓存的数据封装成一个大小合适的 TCP 报文,然后通过 Internet 发送给目的主机的 TCP 进程。目的主机上的 TCP 进程收到 TCP 报文后,将 TCP 报文解封装,取出 TCP 报文中的数据,将其放入应用进程的接收缓存,并且通知应用进程从接收缓存中读取数据。在一次进程数据交互结束时,释放传输连接(四次挥手)。
        TCP 提供拥塞控制功能,目的是防止发送方发送数据的速率超出网络的容量。以及流量控制机制,使接收方能够限制发送方在给定时间内发送的数据量,避免收的速率过低于发的速率。
        与 UDP 一样,TCP 支持多路复用,允许任何主机上的多个应用进程同时与它们各自对等的应用进程进行通信。但TCP面向连接建立的是点到点的连接,因此不支持组播或广播。

2、UDP       

         该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。
        UDP 是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文。
        UDP提供了无连接通信,没有可靠性保证、顺序保证、拥塞控制和流量控制字段等,不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP传输的可靠性由应用层负责。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序。但UDP 支持一对一、一对多、多对一和多对多的交互通信。

 三、编程流程

1、基于套接字的TCP通信流程

服务器端:创建套接字(socket)、绑定本机的IP地址和端口(bind)、设置监听套接字队列(listen)、等待客户端连接(accept\recv)、收发数据(read/write)、关闭套接字(close)。

客户端:创建套接字(socket)、发送连接请求(connect\send)、收发数据(read/write)、关闭套接字(close)。

2、基于套接字的UDP通信流程

服务器端:创建套接字(socket)、绑定本机的IP地址和端口(bind)、收发数据(sendto/recvfrom)、关闭套接字(close)。

客户端:创建套接字(socket)、收发数据(sendto/recvfrom)、关闭套接字(close)。

四、基于套接字的TCP通信流程相关函数

1.socket() 创建套接字

2.bind() 绑定本机的IP地址和端口

补充:

3.listen() 设置监听套接字队列

 4.accept()/recv() 等待客户端连接

5.connect()/send() 发送连接请求

 6.read()/write()收发数据、close()关闭套接字与文件IO一样的用法。

 五、基于套接字的UDP通信流程相关函数,其他函数与TCP一致,主要是收发数据(sendto/recvfrom)。

 

 六、基于套接字的TCP通信

 服务端:

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

int main(int argc, char *argv[])
{
    //创建套接字
    int sockfd=socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd==-1)
    {
        perror("socket");
        exit(-1);
    }
    //绑定IP地址和端口号
    struct sockaddr_in saddr;
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(9999);
    saddr.sin_addr.s_addr=inet_addr("192.168.7.75");
    //saddr.sin_addr.s_addr=INADDR_ANY  //自动获取当前IP
    int ret=bind(sockfd,(struct sockaddr *)&saddr,sizeof(struct sockaddr_in));
    if(ret<0)
    {
        perror("bind");
        exit(-1);
    }
    //设置监听套接字
    int ret2=listen(sockfd,11);
    if (ret2==-1)
    {
        perror("listen");
        exit(-1);
    }
    //等待客户端连接
    while(1)
    {
        struct sockaddr_in caddr;
        memset(&caddr,0,sizeof(caddr));
        int socklen=sizeof(struct sockaddr_in);
        printf("wait for a new client...\n");
        int clientfd=accept(sockfd,(struct sockaddr *)&caddr,(socklen_t*)&socklen);
        if(clientfd==-1)
        {
            perror("accept");
            exit(-1);
        }
        printf("link successful. ip:%s,port:%d,clientfd:%d\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),clientfd);
        char buf[64]={0};
        int num;
        while(1)
        {
            num=read(clientfd,buf,64);
            if (num < 0)
            {
                perror("read");
                exit(-1);
            }
            else if (num == 0 )
            {
                printf("客户端断开。\n");
                break;
            }
            else
            {
                printf("收到了:%s\n",buf);
                memset(buf,0,sizeof(buf));
            }
        }
        close(clientfd);
    }
    close(sockfd);
    return 0;
}

客户端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    //创建套接字
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if (sockfd==-1)
    {
        perror("socket");
        exit(-1);
    }
    //向服务端发起连接
    struct sockaddr_in caddr;
    caddr.sin_family=AF_INET;
    caddr.sin_port=htons(9999);
    caddr.sin_addr.s_addr=inet_addr("192.168.7.75");
    printf("linking...\n");
    int ret=connect(sockfd,(struct sockaddr *)&caddr,sizeof(struct sockaddr_in));
    if(ret<0)
    {
        perror("link");
        exit(-1);
    }
    printf("link successful.\n");
    char buf[64]={0};   
    while(1)
    {
        memset(buf,0,sizeof(buf));
        printf("输入发送的信息(quit 退出):");
        fgets(buf,sizeof(buf),stdin);
        if (strncmp(buf,"quit",4) == 0)
        	break;
        buf[strlen(buf)-1]='\0';
        write(sockfd,buf,64);
    }
    close(sockfd);
    return 0;
}

注:TCP面向连接,故只能一对一通信,想实现多个客服端同时访问服务器,可以考虑多进程、多线程以及IO多路复用等方法。

七、基于套接字的UDP实现时间查询

服务端:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
int main(int argc, char *argv[])
{ 
    //创建套接字
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
    
    //绑定IP地址和端口号
    struct sockaddr_in saddr;
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(9999);
    saddr.sin_addr.s_addr=inet_addr("192.168.7.75");
    int ret=bind(sockfd,(struct sockaddr *)&saddr,sizeof(struct sockaddr_in));
    if(ret<0)
    {
        perror("bind");
        exit(-1);
    }
    printf("bind successful.\n");
    
    //发送或接收
    struct sockaddr_in caddr;
    int s_len=sizeof(caddr);
    char buf[64]={0};
    int num;
    memset(&caddr,0,sizeof(caddr));
    while(1)
    {
        num=recvfrom(sockfd,buf,64,0,(struct sockaddr *)&caddr,&s_len);
        if(num < 0)
        {
            perror("recvfrom");
            exit(-1);
        }
        else
        {
            if (strcmp(buf,"time") == 0)
            {
                struct tm *t;
                time_t tt;
                time(&tt);
                t=localtime(&tt);
                sprintf(buf,"%4d年%02d月%02d日 %02d:%02d:%02d\n",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);
                sendto(sockfd,buf,64,0,(struct sockaddr *)&caddr,sizeof(struct sockaddr_in)); 
            }
            else
                sendto(sockfd,"命令格式错误\n",64,0,(struct sockaddr *)&caddr,sizeof(struct sockaddr_in)); 
        }
    }
    return 0;
} 

客户端:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{ 
    //创建套接字
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd < 0)
    {
        perror("socket");
        exit(-1);
    }
    
    struct sockaddr_in saddr;
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(9999);
    saddr.sin_addr.s_addr=inet_addr("192.168.7.75");
    
    struct sockaddr_in caddr;
    int s_len=sizeof(caddr);
    char buf[64]={0};
    int num;
    while(1)
    {
        
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1]='\0';
        sendto(sockfd,buf,64,0,(struct sockaddr *)&saddr,sizeof(struct sockaddr_in));
        memset(buf,0,sizeof(buf));
        num=recvfrom(sockfd,buf,64,0,(struct sockaddr *)&caddr,&s_len);
        printf("%s\n",buf);
    }
    close(sockfd);
    return 0;
} 

注:UDP面向无连接,故服务端可同时接收多个客户端信息。


版权声明:本文为weixin_50595937原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。