Linux--套接字编程

一、前言

1.套接字编程:

(1)做什么:编写网络通信程序,实现两个主机上进程之间的数据传输。

网络通信也是进程间通信方式的一种:相较于以前的进程间通信可以实现跨主机 ,之前的所学习的进程间通信方式-管道,共享内存等是在同一个主机上不同进程间进行进程间通信。

(2)网络通信方式也是进程间通信方式的一种,但是通信两端有不同的角色:

客户端:通常指的是,部署在客户机上,发起业务请求的一端。

服务端:通常指的是,部署在服务提供商的服务器上,向客户端提供服务器。

(3)网络通信也分为两种不同的架构:

  1. C/S:客户端服务器模式(client/server)

通常程序员既要实现服务器端程序 也要实现客户端程序

谁安装了客户端 谁才能与服务器通信

2.B/S:浏览器服务器模式(browser/server)

通常程序员只需要只需要服务端程序,客户端不需要实现(客户端就是浏览器)


通俗的来说,客户端就是客户需要使用的程序软件,服务器就是开发供应商所提供的,在C/S架构下,客户需要使用什么程序就需要安装下载这个软件,所以,也就是程序员需要书写客户端程序,同时需要书写服务端程序。而B/S架构下,只需要程序员书写服务端程序的原因是每台计算机本身就自带有浏览器,所以在使用浏览器这个软件的时候就不需要再编写这个程序,让客户安装下载,计算机本身就已经存在。


我们学习的网络编程,叫做套接字编程(socket编程)

学习内容:

1.基于socket套接字相关的接口实现网络通信客户端与服务器程序

  1. Socket套接字:一套网络通信API接口

3.通信程序的编写流程-主要关心的是传输层通信程序的编写

  1. 传输层两个典型协议:TCP+UDP

二、正式学习

Linux下一切皆是文件,所有操作都是文件操作,包括网络了通信。

++Socket API 接口:++

  1. 创建套接字:在内核中创建网络通信描述信息,并获取操作句柄-文件描述符(句柄就是文件描述符)创建套接字核心其实就是,套接字的文件描述符( 内核中创建socket结构体变量)。

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

① domain:地址域 描述是什么样的通信 也就是 通信地址类型

比如:

AF_UNIX:域间通信(本地通信)struct sockaddr_un(不跨主机 在同一主机之间进行通信)

AF_INET:IPV4通信struct sockaddr_In

AF_INET6:IPV6通信 struct sockaddr_In6


② type:套接字类型

比如:

SOCKET_STREAM:流式套接字 有序的 可靠的 双向的 基于字节流

SOCKET_DGRAM:数据报套接字 无连接 不可靠 有最大的长度限制

SOCKET_RAW:提供更底层的原始的数据包捕获


③ protocol:协议类型 选择要使用的协议

默认值0:SOCKET_STREAM是TCP协议 SOCKET_DGRAM是UDP协议

IPPROTO_TCP:TCP协议 IPPROTO_UDP:UDP协议

返回值:成功 则返回操作语柄套接字描述符; 失败返回1

2.为套接字绑定地址信息:

设置发送数据时的源端地址信息(当前设备网卡IP地址+端口号)端口号:0~1024是预留端口(需要管理员权限)告知操作系统,网卡收到那些数据应该交给当前套接字进行处理

告诉网卡,发送数据的时候,数据的源端地址信息是什么

接收数据的时候 哪些数据应该交给我来处理

Int bind(int sockefd,struct sockaddr* addr,socklen_t len)

sockfd:套接字描述符 socket返回的描述符

addr:地址信息

sockaddr*是通用的地址信息

struct sockaddr_un struct sockaddr_In6

struct sockaddr_In{

Unshort sin_family;// 地址域类型

Unshort sin_port;//要绑定的端口号

Struct in_addr{uint32 s_adrr;}sin_addr;要绑定的ip

}

len:地址信息长度(防止访问越界3

**3.连接服务器------》**只用于TCP客户端

Int connect(int sockefd,struct sockaddr* addr,socklen_t len)

Addr:要连接的服务器地址信息

返回值:成功返回0 失败返回-1

4.开始监听(用于TCP协议的服务端)

目的:告诉操作系统,可以开始接受客户端的连接请求了。

Int listen(int sockfd,int backlog);

Backlog:同一时间能够接受的最大客户端连接请求数量。

限制了同一时间最多有多少客户端能够连接服务器。避免网络攻击,导致资源耗尽。

返回值:成功返回0 失败返回-1。

5.获取就绪连接(用于TCP协议的服务端)

作用:获取与指定客户端进行通信的操作句柄(描述符)

Int accept (int sockfd,struct sockaddr*peer_addr,socklen_t*len);

peer_addr:获取指定对端地址信息(这里对端就是客户端)

len:既要设置获取多长的地址,并且还要返回实际的地址信息长度

函数的参数分为三类:输入型参数 输出型参数 输入输出型参数

返回值:成功返回0 失败返回-1

6.接收数据:接收数据的接口是一个阻塞接口(recv的时候没有数据则等待)

分为两类:

TCP协议的接口:ssize_t recv(int sockfd,void* buf,size_t len,int flag)-》其实就是干了这样一件事,找到对应的套接字的描述符,从这个套接字的缓冲区将数据接收过来。

在此之前,客户端要发送数据到服务器网卡上,一开始就会创建一个套接字,这个套接字就会有一个完整的五元组,和一个接收缓冲区,一个发送缓冲区,在客户端发送数据的时候给这个数据也有完整的五元组,描述了数据从哪来到哪去,其中数据的dip和网卡对应上才能将这个数据发送到对应网卡上去,通过dport来定位这个数据应该交给哪一个通信程序或者套接字,如果数据中保存的dport和一个socket中的sport对应那么这个数据就是要交给这个socket来处理的。之后就是recv的过程。在接收数据后就应该从缓冲区里面删除掉。

Sockfd:对于客户端来说就是socket的返回值

对于服务端来说就是accept的返回值

Flag:标志位

比如:

MSG_DONTWAIT:设置非阻塞等待 如果本次recv能拿到数据则则直接返回,没有接收到数据就报错返回。

MSG_PEEK:获取接收数据 但是不从缓冲区删除数据。常用于一种探测型接收。

返回值:成功则返回实际读取到的数据长度 、出错则返回-1 、TCP通信断开 返回0。

UDP协议的接口:sszize_t recvfrom(int sockfd,void* buf,size_t len,int flag,struct sockaddr*peer_addr,socklen_t addrlen)

peer_addr:输出参数 返回对端地址信息 表示该数据是谁发送过来的

返回值:成功则返回实际读取到的数据长度 出错则返回-1 不会返回0 因为他是数据报是无连接的。

7.发送数据:发送数据相关的接口也是阻塞接口(发送过快 数据将socket发送缓冲区满了就会阻塞)

Ssize_t send(int sockfd,char*data,size_t len,int flag);

Ssize_sento(int sockfd,char*data,size_t len,int flag,struct sockaddr*toaddr,socklen_t addrlen);

Toaddr:描述数据要发送给谁

返回值:实际要发送的数据长度 出错则返回-1

8.关闭套接字:int close(int fd);

字节序转换接口:

字节序转换针对的数据类型: short int long float double 无符号

两种:

1. 从主机字节序转换为网络字节序:

2字节数据转换Uint16_t htons(uint16_t val);

4字节数据的转换:uint32_t htonl(uint32_t val);

注意:短整型数据转换不能使用 htonl

例如:

假设数据在是0X01 02,主机字节序固定小端存储,低地址存低位,高地址存高位,所以内存存放就是02 01,网络字节序固定大端存储,低地址存高位,高地址存低位。所以在内存存储的时候就会是01 02进行存放,但是C语言有一个操作叫做隐式类型转换,会将除了long long 以外的其他数据看成4个字节的数据,根据对应的数据类型截取对应的字节大小。

2.从网络字节序转换为主机字节序:

2字节数据转换Uint16_t ntohs(uint16_t val);

4字节数据的转换:uint32_t ntohl(uint32_t val);

IP地址的特殊性:

本质就是4字节的无符号整数 :ntohl , htonl,进行转换即可

但是IP为了能够可以更加便于记忆 会采用点分十进字符串进行表示:192.168.12.12

所以很多是时候 IP地址的转换 并不是给于实际的数字 而是一个字符串

Struct in_addr inet_addr(const char*ipstr);

将字符串点分十进制IP地址 直接转换为网络字节序数字IP地址

Const char*inet_ntoa(struct in_addr addr);

将网络字节序数字IP地址 转换为字符串点分十进制IP地址

Inet_ntoa这个接口是非线程安全的 因为会造成交叉写入

Const char*inet_ntop(int domain,void* src,char*dst,size_t size)

功能:将网络字节序整数IP地址准换为字符串IP地址

Domain:AF_NET,

UDP通信程序的编写流程:

服务端:

  1. 创建套接字
  2. 为套接字绑定地址
  3. 接收数据 recvfrom(fd,buf,sz,flag,*addr,len);
  4. 向客户端响应数据sendto(data,*addr)
  5. 关闭套接字

客户端:

  1. 创建套接字

  2. 绑定套接字地址-》通常客户端并不主动绑定地址

  3. 发送数据 sendto(data,*addr)-》隐含操作:检测套接字是否已经绑定地址 如果未绑定,则从闲置端口池获取一个闲置的顿口+IP进行绑定

  4. 接收响应recvfrom

  5. 关闭套接字

代码展示:

/*

编写一个简单的UDP通信服务端程序:

功能:模拟简单的聊天程序,

1.接收客户端进行展示。

2.从标准输入获取一个信息响应给客户端。

*/

编写过程需要用到的头文件(服务端和客户端一样):

cs 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>//close
#include<sys/socket.h>//socket api接口
#include<netinet/in.h>//地址结构和协议宏声明
#include<arpa/inet.h>//字节序转换的api接口

服务端程序:

cs 复制代码
int main(){
    //1.创建套接字
    //int socket(int domain,int type,int protocol)
    //AF_INET:IPV4通信  SOCK_DGRAM:数据报套接子
    int sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(sockfd<0){
        perror("创建套接字失败:");
        return -1;
    }

    //1.5构造地址信息结构,填充要绑定的地址信息 绑定的ip地址必须是当前已有的网卡ip地址,绑定那个网卡则接收
    struct sockaddr_in addr;//定义IPV4的地址结构
    //向这个地址进行赋值
addr.sin_family=AF_INET;
addr.sin_port=htons(9090);//要转换为网络字节序
//in_addr_t inet_addr(const char*cp)
//int inet_pton(int af,char *cp,void*buf)
addr.sin_addr.s_addr=inet_addr("192.168.14.128");
inet_pton(AF_INET,"192.168.14.128",&addr.sin_addr);
    //2.为套接子绑定地址信息(',"".
    //int bind(int fd, const struct sockaddr *addr, socklen_t len)
    socklen_t addrlen=sizeof(struct sockaddr_in);

      int ret=bind(sockfd,(struct sockaddr*)&addr,addrlen); 
      if(ret<0){
        perror("绑定地址信息失败");
        return -1;
      }
      while(1){
         //3.接受客户端请求 recvfrom 不仅仅接收数据 还接受发送端地址
         //ssize_t recvfrom(fd, *buf, n, flags, *addr, *addr_len)
        struct sockaddr_in peerAddr;
        socklen_t addrlen=sizeof(struct sockaddr_in);
        char buffer[1024]={0};//用于存放获取出来的数据

         ret=recvfrom(sockfd,buffer,1023,0,(struct sockaddr*)&peerAddr,&addrlen);
         if(ret<0){
            perror("recvfrom接受数据失败");
            continue;
         }
         printf("客户端[%s:%d]say:%s\n",
         inet_ntoa(peerAddr.sin_addr),ntohs(peerAddr.sin_port),buffer);
         //4.捕获键盘输入
         memse(buffer,0x00,1024);
         printf("服务端 say:");
         fflush(stdout);
         scanf("%s",buffer);

         //5.向客户端响应数据
         //ssize_t sendto(fd, *data, n, flags,*dst_addr, addr_len)
         ret=sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&peerAddr,addrlen);
            if(ret<0){
                perror("sendto 发送数据失败");
                continue;
            }
      }

      //关闭套接子
      close(sockfd);
      return 0;
      

}

客户端程序:

cs 复制代码
#include<stdio.h>
#include<string.h>
#include<unistd.h>//close
#include<sys/socket.h>//socket api接口
#include<netinet/in.h>//地址结构和协议宏声明
#include<arpa/inet.h>//字节序转换的api接口

#define CHECK_RESULT(ret,msg)if((ret)<0){peeror("msg");return -1;}
int main(){
    //1.创建套接子
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    CHECK_RESULT(sockfd,"创建套接子失败");
    //2.为套接字绑定地址信息 一般客户端不主动绑定
    //3.捕获键盘输入
    char buffer[1024]={0};
    printf("客户端 say:");
    fflush(stdout);
    scanf("%s",buffer);

    //4.发送数据 
    struct sockaddr_in srvAddr;
    srvAddr.sin_family=AF_INET;
    srvAddr.sin_port=htons(9090);
    srvAddr.sin_addr.s_addr=("192.168.14.128");
    socklen_t addrlen=sizeof(struct sockaddr_in);
while(1){
    ssize_t ret=sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&srvAddr,addrlen);
    CHECK_RESULT(ret,"发送数据失败");


    //5.接受服务端响应,进行展示
    memset(buffer,0x00,1024);
    ret=recvfrom(sockfd,buffer,1023,0,(struct sockaddr*)&srvAddr,&addrlen);
     CHECK_RESULT(ret,"接受响应失败");
     printf("服务端 say:%s\n",buffer);
}
    //6.关闭套接子
    close(sockfd);
    return 0;
}

Tcp 通信程序的流程:

Tcp通信协议:面向连接,可靠传输,面向字节流

面向连接:两端必须先建立连接,才能通信

可靠数据:确保数据能够安全,有序的到达对端

面向字节流:以字节为单位进行传输,不限制传输大小,先发先到

客户端:

  1. 创建套接字:让进程与网卡关联
  2. 为套接字绑定地址信息
  3. 开始监听(listen(sockfd,int backlog)告诉操作系统,我可以开始接收客户端的连接请求了)backlog:限制时间并发连接请求数
  4. 获取新链接fd = accpt(lstfd, *addr, *addrlen)将就绪的新链接,从内核的就绪连接队列中取出,并返回连接socket对应的描述符(通信套接字描述符)
  5. .通过accept获取的新链接与客
    recv/sendrecv(fd, buf, len, flag);因为新建的每个通信套接字中都有完整的五元组可以通过这个socket知道与哪个客户端通信
  6. 关闭套接字

客户端:

1.创建套接字

  1. 为套接字绑定地址根据需要选择是否主动绑定

3.连接服务器connect(fd,srvaddr, addlen)

4.与服务端进行通信recv/send

5.关闭套接字

程序编写:

初步编写(有瑕疵)

服务端:

1.所需的头文件

cs 复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>//字节序转换

2.正式代码:

cs 复制代码
#define CHECK_RESULT(ret,msg)\
if(ret<0){perror(msg);return -1;}

int main(){
    //1.创建套接子

    //int socket(int domian,int type,int protocol)
    int listen_fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    CHECK_RESULT(listen_fd,"创建套接子失败");
    //2.构造监听地址结构对象 为套接子绑定地址信息
    //int bind(int sockfd,struct sockaddr*addr,socklen_t len)
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(9000);
    addr.sin_addr.s_addr=inet_addr("192.168.14.128");
    socklen_t addrlen=sizeof(struct sockaddr_in);
    int ret=bind(listen_fd,(struct sockaddr*)&addr,addrlen);
    //3.开始监听
    //int listen(int sockfd,int backlog)
    ret =listen(listen_fd,1024);
    CHECK_RESULT(ret,"监听失败");
    while(1){
    //4.获取新连接(与客户端的通信都是通过获取新连接进行的
    //int accept(listen_fd,struct sockaddr*peerAddr,socklen_t*len);
    //listenfd :监听套接字描述符
    //*peerAddr:客户端地址信息;
    //*len :指定想要获取的地址长度 以及返回实际获取的地址长度
    //返回值:新建连接的套接子描述符(用于往后的数据通信)
    struct sockaddr_in peerAddr;
    socklen_t addrlen=siezeof(peerAddr);
    int newfd=accept(listen_fd,(struct sockaddr*)&peerAddr,&addrlen);
    if(newfd<0){
        perror("获取新连接出错");
        continue;
    }
    char peer_ip[24]={0};
    inet_ntop(AF_INET,&peerAddr.sin_addr,peer_ip,23);//将指定类型的地址信息 转换成字符串类型的ip地址
    printf("新的客户端建立成功:[%s;%d]\n",
    peer_ip,ntohs(peerAddr.sin_port));
    //5.接收客户端消息,进行展示
    // ssize_t recv(int fd,char*buff,size_t len,int flag) 
    //返回值:成功则返回实际接受的数据长度
    //失败出错,则返回-1 若返回0 表示连接断开
    char buffer[1024]={0};
    ret=recv (newfd,buffer,1023,0);
    if(ret<0){
        perror("接受数据出错");
        continue;
    }
    else if(ret==0){
        printf("与客户端连接断开\n");
        close(newfd);
        continue;
    }
    printf("客户端say:%s\n",buffer);
    //6.捕获键盘输入,向客户段发送数据
    memset(buffer,0x00,1024);
    printf("输入数据:");
    fflush(stdout);
    scanf("%s",buffer);
    ret=send(newfd,buffer,strlen(buffer),0);
    if(ret<0){
        perror("发送数据出错");
        colse(newfd);
        continue;
    }
    printf("数据发送不完整\n");
    }
    //7.关闭套接子
    close(listen_fd);
    return 0;
}

客户端:

1.需要的头文件

cs 复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>//字节序转换

2.主代码:

cs 复制代码
#define CHECK_RESULT(ret,msg)\
if(ret<0){perror(msg);return -1;}

int main(){
    //1.创建套接子
    int sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    CHECK_RESULT(sockfd,"创建套接子失败");
    //2.绑定地址信息(不主动)
    //3.向服务器发送连接请求
    //int connect(int fd,struct sockaddr*addr,socklen_t len)
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(9000);
    addr.sin_addr.s_addr=inet_addr("192.168.14.128");
    socklen_t addrlen=sizeof(struct sockaddr_in);
    int ret=connect(sockfd,(struct sockaddr*)&addr,addrlen);
    CHECK_RESULT(ret,"连接服务器失败");
    while(1){
        //4.捕获键盘数据,向服务器发送数据
        char buffer[1024]={0};
        memset(buffer,0x00,1024);
        printf("输入数据:");
        fflush(stdout);
        scanf("%s",buffer);
        ret=send(sockfd,buffer,strlen(buffer),0);
        if(ret<0){
            perror("发送数据失败");
            close(sockfd);
            return -1;
        }
        //5.等待服务器响应 展示响应数据
        memset(buffer,0x00,1024);
        ret=recv(sockfd,buffer,1023,0);
        if(ret<0){
            perror("接收数据失败");
            colse(sockfd);
            return -1;
        }
        printf("服务端say:%s\n",buffer);
    }
    
    //6.关闭套接子
    close(sockfd);
    return 0;
}

但是这个代码有瑕疵:

当前程序存在问题:

1.程序流程固定(获取新连接,接收数据,发送数据),导致同一时间只能与一个客户端通信一次(循环上去就会等待获取下一个新连接)

当前我们的tcp客户端服务端程序,在实现了之后存在问题:

要么服务端只能与一个客户端持续通信

要么服务端只能与一个客户端通信一次

原因:

1.因为我们的代码是串行的

2.accept和recv 是阻塞接口

并发tcp服务器解决方案:多执行流方案

1.让主控流程(主线程/主进程)只负责一件事:获取新连接

2.每获取一个新连接,都为其通信创建一个新的执行流(进程/线程)

实现每一个客户端的通信,都有一个独立的执行流负责管理,互不影响。

方案:

多进程版:独立性强,稳定性,健壮性强

cs 复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>//字节序转换

#define CHECK_RESULT(ret,msg)\
if(ret<0){perror(msg);return -1;}

//实现一个多进程版本的服务器:创建进程,在子进程中与客户端进行通信
void worker(int sockfd){
    pid_t pid=fork();
    if(pid<0){
        perror("创建子进程失败");
        return;
    }
    if(pid>0){

        return;
    }
    while(1){

    
    char buffer[1024]={0};
    int ret=recv (sockfd,buffer,1023,0);
    if(ret<0){
        perror("接受数据出错");
        continue;
    } 
    else if(ret==0){
        printf("与客户端连接断开\n");
        close(sockfd);
        break;
    }
    printf("客户端say:%s\n",buffer);
    //6.捕获键盘输入,向客户段发送数据
    memset(buffer,0x00,1024);
    printf("输入数据:");
    fflush(stdout);
    scanf("%s",buffer);
    ret=send(sockfd,buffer,strlen(buffer),0);
    if(ret<0){
        perror("发送数据出错");
        continue;
    }
    printf("数据发送不完整\n");
}

    close(sockfd);
    exit(0);//当前在子进程中 这里退出的就是子进程
}


int main(){
    //1.创建套接子

    //int socket(int domian,int type,int protocol)
    int listen_fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    CHECK_RESULT(listen_fd,"创建套接子失败");
    //2.构造监听地址结构对象 为套接子绑定地址信息
    //int bind(int sockfd,struct sockaddr*addr,socklen_t len)
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(9000);
    addr.sin_addr.s_addr=inet_addr("192.168.14.128");
    socklen_t addrlen=sizeof(struct sockaddr_in);
    int ret=bind(listen_fd,(struct sockaddr*)&addr,addrlen);
    //3.开始监听
    //int listen(int sockfd,int backlog)
    ret =listen(listen_fd,1024);
    CHECK_RESULT(ret,"监听失败");
    while(1){
    //4.获取新连接(与客户端的通信都是通过获取新连接进行的
    //int accept(listen_fd,struct sockaddr*peerAddr,socklen_t*len);
    //listenfd :监听套接字描述符
    //*peerAddr:客户端地址信息;
    //*len :指定想要获取的地址长度 以及返回实际获取的地址长度
    //返回值:新建连接的套接子描述符(用于往后的数据通信)
    struct sockaddr_in peerAddr;
    socklen_t addrlen=siezeof(peerAddr);
    int newfd=accept(listen_fd,(struct sockaddr*)&peerAddr,&addrlen);
    if(newfd<0){
        perror("获取新连接出错");
        continue;
    }
    char peer_ip[24]={0};
    inet_ntop(AF_INET,&peerAddr.sin_addr,peer_ip,23);//将指定类型的地址信息 转换成字符串类型的ip地址
    printf("新的客户端建立成功:[%s;%d]\n",
    peer_ip,ntohs(peerAddr.sin_port));
    worker(newfd);
    return 0;
}
}

多线程版:成本低,创建效率高,但是稳定性稍低

创建普通线程:pthread_creat(tid,callback)

线程分离------将当前线程分离出去,线程退出后自动释放资源。

这就意味着需要一个并发服务器,可以与多个客户端进行通信的服务器:

为每一个accept和recv/send创建一个独立的执行流-》为每一个客户端的新连接创建一个独立的执行流与其通信。

cs 复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>//字节序转换
#include<pthread.h>
#define CHECK_RESULT(ret,msg)\
if(ret<0){perror(msg);return -1;}
void*entry(void*arg){
    int sockfd=*(int*)arg;//访问地址指向的空间中的数据
     while(1){

    
    char buffer[1024]={0};
    int ret=recv (sockfd,buffer,1023,0);
    if(ret<0){
        perror("接受数据出错");
        continue;
    } 
    else if(ret==0){
        printf("与客户端连接断开\n");
        close(sockfd);
        break;
    }
    printf("客户端say:%s\n",buffer);
    //6.捕获键盘输入,向客户段发送数据
    memset(buffer,0x00,1024);
    printf("输入数据:");
    fflush(stdout);
    scanf("%s",buffer);
    ret=send(sockfd,buffer,strlen(buffer),0);
    if(ret<0){
        perror("发送数据出错");
        continue;
    }
    printf("数据发送不完整\n");
}

    close(sockfd);
    
}


//创建一个线程,负责通过当前描述符与客户端进行通信
void worker(int sockfd){
    pthread_t tid;
    int ret=pthread_creat(&tid,NULL,entry,(void*)sockfd);
    if(ret!=0){
        perror("创建工作线程失败");
        return;
    }

    pthread_detach(tid);//分离线程,让线程推出后自动释放资源,不需要被等待
    
    
}



int main(){
    
    //1.创建套接子

    //int socket(int domian,int type,int protocol)
    int listen_fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    CHECK_RESULT(listen_fd,"创建套接子失败");
    //2.构造监听地址结构对象 为套接子绑定地址信息
    //int bind(int sockfd,struct sockaddr*addr,socklen_t len)
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(9000);
    addr.sin_addr.s_addr=inet_addr("192.168.14.128");
    socklen_t addrlen=sizeof(struct sockaddr_in);
    int ret=bind(listen_fd,(struct sockaddr*)&addr,addrlen);
    //3.开始监听
    //int listen(int sockfd,int backlog)
    ret =listen(listen_fd,1024);
    CHECK_RESULT(ret,"监听失败");
    while(1){
    //4.获取新连接(与客户端的通信都是通过获取新连接进行的
    //int accept(listen_fd,struct sockaddr*peerAddr,socklen_t*len);
    //listenfd :监听套接字描述符
    //*peerAddr:客户端地址信息;
    //*len :指定想要获取的地址长度 以及返回实际获取的地址长度
    //返回值:新建连接的套接子描述符(用于往后的数据通信)
    struct sockaddr_in peerAddr;
    socklen_t addrlen=siezeof(peerAddr);
    int newfd=accept(listen_fd,(struct sockaddr*)&peerAddr,&addrlen);
    if(newfd<0){
        perror("获取新连接出错");
        continue;
    }
    char peer_ip[24]={0};
    inet_ntop(AF_INET,&peerAddr.sin_addr,peer_ip,23);//将指定类型的地址信息 转换成字符串类型的ip地址
    printf("新的客户端建立成功:[%s;%d]\n",
    peer_ip,ntohs(peerAddr.sin_port));
    worker(newfd);
    //线程间资源
}
    return 0;
}
相关推荐
进击的荆棘1 小时前
Linux系统——进程控制(下)
linux·运维·服务器·进程
上海云盾-小余1 小时前
业务被 DDoS 打崩?四层、七层攻击特征与防护方案复盘
网络·网络协议·tcp/ip·ddos
优化Henry1 小时前
学习笔记之关于MME不通问题归纳
运维·网络·学习·5g·信息与通信
Brookty1 小时前
TCP传输效率衡量、ACK延迟机制与窗口效率介绍
网络·tcp
爱网络爱Linux8 小时前
【系列】每天一个 Linux 命令(53)startx 命令
linux·rhce·rhca·linux运维·linux命令大全·红帽认证
ZhengEnCi9 小时前
L2A-一个域名如何访问多台云服务器-子域名、路径前缀与Nginx反向代理选型指南
linux·人工智能
M78佐菲9 小时前
51单片机学习笔记:ds18b20要点整理
linux·笔记·嵌入式硬件·学习·51单片机
高亦真9 小时前
今天是学习嵌入式的第37天
linux·学习·算法
一个有温度的技术博主10 小时前
Linux 核心命令速查指南:从文件操作到系统运维
linux·服务器