进程间通信——SOCKET(TCP)

套接字:基于TCP/IP协议可实现基本网络通信功能的逻辑对象。
机器与机器的通信,或者进程与进程的通信,在这里都可以被抽象地看作是套接字
与套接字的通信。
应用程序编写者无需了解网络协议中的任何细节,更无需知晓系统内核和网络设备的
运作机制,只要把像发送的数据写入套接字,或从套接字中读取想接收的数据即可。

TCP协议:三次握手,四次挥手
TCP保证数据传输的可靠性:
TCP的协议栈底层在向另一端发送数据时,会要求对方在一个给定的时间窗口内返回确认。
如果超过了这个时间窗口仍没有收到确认,则TCP会重传数据并等待更长的时间。只有在
数次重传均失败以后,TCP才会最终放弃。TCP含有用于动态估算数据往返时间的算法,因此
他知道等待一个确认需求时间。
TCP保证数据传输的有序性:
TCP的协议栈底层在向另一端发送数据时,会为所发送数据的每一个字节指定一个序列号。
即使这些数据字节没有能够按照发送时的顺序到达接收方,接收方的TCP也可以根据他们
的序列号重新排序,再把最后的结果交给应用程序。
TCP是全双工的:
在给定的连接上,应用程序在任何时候即可以发送数据也可以接收数据。因此,TCP必须跟踪
每个方向数据流的状态信息,如序列号和通告窗口的大小。

#include<sys/scoket.h>
int scoket(int domain,int type,int protocol)
功能:创建套接字
参数:domain:通信域,协议族,可取以下值:
    PF_LOCAL/PF_UNIX  本地套接字,进程间通信
    PF_INET      基于IPv4的网络通信
    PF_INET6    基于IPv6的网络通信
    PF_PACKET  基于底层包的网络通信
          type:套接字类型,取值如下:
    SOCK_STREAM     流式套接字,基于TCP协议   
    SOCK_DGRAM      数据报套接字,基于UDP协议
    SOCK_RAW           原始套接字,工作在传输层以下
          protocol:特殊协议,对于流式和数据报套接字而言,只能取0
返回值:成功返回表示套接字对象的文件描述符,失败返回-1.

基本地址结构,本身没有意义,仅用于泛型化参数
struct sockaddr
{
    sa_family_t  sa_family;//地址族
    char   sa_data[14];//地址值
};

本地地址结构,用于AF_LOCAL/AF_UNIX域的本地通信
struct sockaddr_un
{
    sa_family_t  sun_family;//地址族(AF_LOCAL/AF_UNIX)
    char   sun_path[];//本地套接字文件的路径
};

网络地址结构,用于AF_INET域的IPv4网络通信
struct sockaddr_in
{
    sa_family_t sin_family;//地址族(AF_INET)
    int_port_t  sin_port;//端口号(0~65535)
    struct   in_addr    sin_addr;//IP地址
};
struct  in_addr
{
    in_addr_t   s_addr;
};
typedef uint16_t in_port_t;//无符号16位整数
typedef uint32_t in_addr_t;//无符号32位整数

字节序转换(大小端问题):
uint32_t htonl(uint32_t hostlong);//长整型主机字节序到网络字节序
uint32_t ntohl(uint32_t netlong);//长整型网络字节序到主机字节序
uint16_t htons(uint16_t hostshort);//短整型主机字节序到网络字节序
uint16_t ntohs(uint16_t netshort);//短整型网络字节序到主机字节序

in_addr_t inet_addr(char const* ip);//点分十进制字符串地址->网络字节序形式整数地址
int inet_aton(char const*ip,struct in_addr*nip);//点分十进制字符串地址->网络字节序形式整数地址
char* inet_ntoa(struct in_addr nip);//网络字节序形式整数地址->点分十进制字节序地址

绑定地址:
#include<sys/socket.h>
int bind(int sockfd,struct sockaddr const*addr,socklen_t addrlen);
功能:将套接字和本机的地址结构绑定在一起
参数:sockfd:套接字描述符
          addr:自己的地址结构
          addrlen:地址结构的字节数
返回值:成功返回0,失败返回-1。

启动监听:
#include<sys/socket.h>
int listen(int sockfd,int backlog)
功能:启动侦听:在指定套接字上启动连接请求的侦听功能
参数:sockfd:套接字描述符,在调用此函数之前是一个主动套接字,是不能
感知连接请求的,在调用此函数并成功返回后,是一个被动套接字,具有感知
连接请求的能力。
          backlog:未决连接请求队列的最大长度,一般取不小于1024的值。
返回值:成功返回0,失败返回-1。

请求连接:
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr const*addr,socklen_t addrlen);
功能:将套接字和对方的地址结构连接在一起
参数:codkfd:套接字描述符
          addr:对方的地址结构
          addrlen:地址结构字节数
返回值:成功返回0,失败返回-1。

等待连接:
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr* addr,socklen_t*addrlen);
功能:等待并接受连接请求,在指定套接字上阻塞,直到连接建立完成
参数:sockfd:侦听套接字描述符
          addr:输出连接请求发起方的地址信息。
          addrlen:输出连接请求发起方的地址信息字节数
返回值:成功返回可用于后续通信的连接套接字描述符,失败返回-1.

发送数据:
#include<sys/socket.h>
ssize_t send(int sockfd, void const*buf,size_t count,int flags);
功能:发送数据
参数:若flags取0则与write函数完全等价,另外也可取以下值:
         MSG_DONTWAIT   以非阻塞方式接收数据
         MSG_OOB    接收带外数据
         MSG_DONTROUTE    不查路由表,直接在本地网络中寻找目的主机
返回值:成功返回实际发送字节数,失败返回-1;

接收数据
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buf,size_t count,int flags);
功能:接收数据
参数:若flags取0则与read函数完全等价,另外也可取以下值:
         MSG_DONTWAIT   以非阻塞方式接收数据
         MSG_OOB    接收带外数据
         MSG_WAITALL    等待所有数据,即不接受到count字节就不返回
返回值:成功返回实际接收字节数,失败返回-1;

//基于tcp协议的服务器
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<errno.h>
#include<signal.h>
#include<sys/wait.h>
//信号处理函数
void sigchild(int signum)
{
    for(;;)
    {
        pid_t pid = waitpid(-1,NULL,WNOHANG);//非阻塞方式回收子进程
        if(pid == -1)
        {
            if(errno == ECHILD)
            {
                printf("没有子进程可回收\n");
                break;
            }
            else
            {
                perror("waitpid");
                exit(-1);
            }
        }
        else if(pid == 0)
        {
            printf("子进程在运行,没法收\n");
            break;
        }
        else
        {
            printf("回收%d进程僵尸\n",getpid());
        }
    }
}

int main(void)
{
    //捕获17号信号
    if(signal(SIGCHLD,sigchild) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    //创建侦听套接字
    printf("服务器:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_STREAM,0);//返回侦听套接字
    if(sockfd == -1)
    {
        perror("socket");
        return -1;
    }
    printf("sockfd:%d\n",sockfd);
    //组织地址结构,代表服务器
    printf("服务器:组织地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(8888);//注意大小端问题,存是小端,服务器大端方式拿。
    //ser.sin_addr.s_addr = inet_addr("127.0.0.1")
    ser.sin_addr.s_addr = INADDR_ANY;//表示接受任意IP下的地址
    //绑定套接字和地址结构
    printf("服务器:绑定套接字和地址结构\n");
    int sockbind = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
    if(sockbind == -1)
    {
        perror("bind");
        return -1;
    }
    //开启侦听功能
    printf("服务器:开启侦听功能\n");
    int socklisten = listen(sockfd,1024);
    if(socklisten == -1)
    {
        perror("listen");
        return -1;
    }
    //等待并接收客户端连接
    for(;;)
    {   //父进程负责和客户端建立通信
        printf("服务器:等待并接收客户端连接\n");
        struct sockaddr_in cli;//用来输出客户端的地址结构
        socklen_t len = sizeof(cli);
        int conn = accept(sockfd,(struct sockaddr*)&cli,&len);//返回后续用来通信的通信套接字
        if(conn == -1)
        {
            perror("accept");
            return -1;
        }
        printf("服务器:接收到%s:%hu的客户端\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
        //子进程负责和客户端通信
        pid_t pid = fork();
        if(pid == -1)
        {
            perror("fork");
            return -1;
        }
        if(pid == 0)
        {
            close(sockfd);//子进程不用侦听套接字,关闭
            //业务处里(接发数据)
            printf("服务器:接发数据\n");
            //接收客户端发来的小写的串
            while(1)
            {
                char buf[128] = {};
                ssize_t size = read(conn,buf,sizeof(buf) - sizeof(buf[0]));
                if(size == -1)
                {
                    perror("read");
                    return -1;
                }
                if(size == 0)
                {
                    printf("服务端:客户端断开连接\n");
                    break;
                }
                for(int i = 0; i < size; i++)
                {
                    //转换大写
                    buf[i] = toupper(buf[i]);
                }
                //发送给客户端
                if(write(conn,buf,size) ==-1)
                {
                    perror("write");
                    return -1;
                }
            }
            //关闭套接字
            printf("服务器:关闭套接字\n");
            close(conn);
            return 0;
        }
        close(conn);//父进程关闭通信套接字
    }
    close(sockfd);
    return 0;
}
//基于TCP协议的客户端
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>

int main()
{
    //创建服务器套接字
    printf("客户端:创建套接字\n");
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd ==-1)
    {
        perror("socket");
        return -1;
    }
    //组织服务器的地址结构
    printf("客户端:组织服务器的地址结构\n");
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(8888);
    ser.sin_addr.s_addr = inet_addr("127.0.0.1");//不能像服务器宏一样
    //发起连接
    printf("客户端:发起连接\n");
    if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1 )
    {
        perror("connect");
        return -1;
    } 
    //业务处理
    printf("客户端:业务处理\n");
    for(;;)
    {
        char buf[128] = {};
        fgets(buf,sizeof(buf),stdin);
        //循环退出条件
        if(strcmp(buf,"!\n") == 0)
        {
            break;
        }
        //将小写传发送给服务器
        if(send(sockfd,buf,strlen(buf),0) == -1 )
        {
            perror("send");
            return -1;
        }

        //接收服务器回传的大写的串
        if(recv(sockfd,buf,sizeof(buf) - sizeof(buf[0]),0) == -1)
        {
            perror("recv");
            return -1;
        }
        printf(">>%s",buf);
    }
    //关闭套接字
    printf("客户端:关闭套接字\n");
    close(sockfd);
}
服务器:
服务器:创建套接字
sockfd:3
服务器:组织地址结构
服务器:绑定套接字和地址结构
服务器:开启侦听功能
服务器:等待并接收客户端连接
服务器:接收到127.0.0.1:57272的客户端
服务器:等待并接收客户端连接
服务器:接发数据
服务器:接收到127.0.0.1:57274的客户端
服务器:等待并接收客户端连接
服务器:接发数据
服务器:接收到127.0.0.1:57276的客户端
服务器:等待并接收客户端连接
服务器:接发数据
服务端:客户端断开连接
服务器:关闭套接字
回收13715进程僵尸
子进程在运行,没法收
服务端:客户端断开连接
服务器:关闭套接字
回收13715进程僵尸
子进程在运行,没法收
服务端:客户端断开连接
服务器:关闭套接字
回收13715进程僵尸
没有子进程可回收



客户端:
客户端:创建套接字
客户端:组织服务器的地址结构
客户端:发起连接
客户端:业务处理
fwefw
>>FWEFW
egetg
>>EGETG
!
客户端:关闭套接字



客户端:创建套接字
客户端:组织服务器的地址结构
客户端:发起连接
客户端:业务处理
^[FEFWfw
greg
>>GREG
etgte
>>ETGTE
!
客户端:关闭套接字

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux进程间通信可以使用socket来实现。Socket是一种特殊的文件,它是应用层与TCP/IP协议族通信的间软件抽象层,提供了一组简单的接口来组织数据,以符合指定的协议。在网络编程,大部分的通信都是通过socket实现的。 使用TCP/IP协议的应用程序通常采用socket接口来实现网络进程之间的通信。无论是UNIX BSD的套接字还是UNIX System V的TLI(已经被淘汰),几乎所有的应用程序都是采用socket来进行通信。 此外,还有一种叫做Unix domain sockets的通信方式,它使用系统文件的地址作为进程间通信的身份,并且仅在系统内核内部进行通信,不会在网络传播。两个进程可以同时打开一个Unix domain socket来进行通信。 总结来说,Linux进程间通信可以通过socket来实现,使用TCP/IP协议的应用程序通常采用socket接口进行通信,并且还可以使用Unix domain sockets进行通信。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [LINUX进程网络通信--SOCKET](https://blog.csdn.net/qq_44370382/article/details/107959541)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [About AF_LOCAL in Linux](https://blog.csdn.net/frank_jb/article/details/77199834)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值