目录

通信篇Socket通信机制

【通信篇】【Socket通信机制】

系列文章


1.简介

Socket通信机制是一种计算机网络通信协议,它是基于TCP/IP协议栈的应用层协议,提供了一组API(应用程序接口),允许不同的进程在不同计算机之间进行通信。socket通信一般涉及客户端和服务端两个socket。服务器端首先初始化Socket,然后与端口绑定,并对该端口进行监听。当客户端连接成功后,客户端会发送数据请求,服务器端接收并处理这些请求,然后将回应数据发送给客户端。客户端读取数据后,连接关闭。

socket本身不是协议,是api接口,通过socket,我们可以使用Tcp/ip协议。

https://i-blog.csdnimg.cn/blog_migrate/ca10e5232d0ec36843ef6d62d13f8db8.png

图片来自网络(如有侵权,联系我,会删除)

socket流程图

https://i-blog.csdnimg.cn/blog_migrate/756a3ddf73c4a2956e6571aee577439e.png

图片来自网络(如有侵权,联系我,会删除)

1.1 Tcp协议

Tcp协议:TCP(Transmission Control Protocol)协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。它旨在适应支持多网络应用的分层协议层次结构,确保数据在传输过程中的可靠性、完整性和顺序性。

1.1.1 Tcp协议的工作原理

1.三次握手建立连接:

第一步:客户端发送一个请求(SYN包)到服务器。

第二步:服务器收到请求后,发送确认和自己连接的请求(SYN/ACK包)给客户端,

第三步:客户端再发送确认(ACK包)连接成功。

socket中TCP协议三次握手如下:

https://i-blog.csdnimg.cn/blog_migrate/c02697f979fdfe058a622068dbde50a6.png

2.数据传输:

发送方将数据划分成小块(称为报文段),并添加头部和校验等信息,然后通过TCP协议将这些报文段发送给接收方。接收方收到报文段后,校验数据的完整性,并把它们重新组装成完整的数据流。

3.四次挥手关闭连接:

第一次挥手:客户端发送断开连接请求,客户端发送一个包含FIN标志的报文,表示客户端已经完成了数据的发送,并请求关闭连接,等待服务端的确认。

第二次挥手:服务端受到客户端发送的FIN标志的报文,发送一个确认(ACK)报文,服务器此时可能还有剩余的数据要发送给客户端。因此服务端继续发送数据,并进入close-wait(等待关闭)状态。而客户端受到确认后,tcp会处于半关闭状态,即:客户端不能发送数据,但是还可以接受数据。

第三次挥手:当服务端完成数据发送以后,服务端发送一个FIN报文给客户端,即服务端向客户端发送断开连接请求。此时服务器进入LASK_ACK(最后确认状态),等待客户端的确认。

第四次挥手:客户端受到服务端的FIN报文后,再次发送一个确认(ACK)报文。然后客户端进入TIME_WAIT(时间等待状态),为了防止网络堵塞,确保服务端已经受到确认报文。然后服务端受到ACK应答报文后,服务端就进入了close状态。此时客户端处于TIME_WAIT状态,此时tcp仍未释放,需要等待2MSL后,客户端才进入CLOSE状态。

MSL:代表报文在网络上存在的最长时间,如果超过此时间,报文会被丢弃,如果是TCP协议,路由器会触发重新发送的机制。

https://i-blog.csdnimg.cn/blog_migrate/424d86ec2ced0968cd0f8c7e55e097e7.png

1.2 IP协议

IP协议,即Internet Protocol,是TCP/IP体系中的网络层协议。

2.socket如何用?

2.1 服务端

2.1.1 步骤

第一步:创建一个socket

    1.int socket(int domain, int type, int protocol);
    //创建一个套接字
    //domain:协议族,AF_INET、AF_INET6、AF_UNIX,AF_INET代表是ipV4,AF_INET6代表是ipv6
    //type:socket类型。SOCK_STREAM、SOCK_DGRAM
    //protocol:协议,0 使用type对应的默认协议
    //返回值:
    //成功: 新套接字所对应文件描述符
    //失败: -1 errno

第二步:初始化serviceaddr,然后bind

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    //给socket绑定一个地址结构 (IP+port)
    //sockfd:socket 函数返回值
    //struct sockaddr_in addr;
    //addr.sin_family = AF_INET;
    //addr.sin_port = htons(8888);
    //addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //addr: 传入参数(struct sockaddr *)&addr
    //addrlen: sizeof(addr) 地址结构的大小。
    //返回值:
    //成功:0
    //失败:-1 errno

第三步:通过listen监听socket,用于监听client发送的连接请求。

    int listen(int sockfd, int backlog);
    //设置同时与服务器建立连接的上限数(同时进行3次握手的客户端数量)
    //sockfd:socket 函数返回值
    //backlog:上限数值,最大值 128
    //返回值:
    //成功:0
    //失败:-1 errno

第四步:通过accept接受客户端的请求。阻塞服务端,等待客户端的连接。

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    //阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。
    //sockfd:socket 函数返回值
    //addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)

    //socklen_t clit_addr_len = sizeof(addr);
    //addrlen:传入传出参数,&clit_addr_len
    //入:addr的大小。 出:客户端addr实际大小。

    //返回值:
    //成功:能与客户端进行数据通信的 socket 对应的文件描述。
    //失败: -1 , errno

第五步:读取客户端发送的数据。

ssize_t read(int fd, void *buf, size_t count);
//fd:是一个文件描述符,在socket编程中就是socket的文件描述符。
//buf:是一个指向缓冲区的指针,数据将从socket中读取到这个缓冲区中。
//count:指定了最多读取的字节数。
//返回值:
//-1: 表示读取过程中出现了错误
// 0: 如果read函数返回0,这通常意味着对端已经关闭了连接,或者是一个非阻塞的socket在没有数据可读时
//   返回的结果。对于TCP连接,这通常意味着对端已经发送了一个FIN包来关闭连接,并且本地socket也接收
//   到了这个FIN包。
//正整数:如果read函数成功读取了一些数据,它将返回读取到的字节数。

第六步:write向客户端发送数据。

ssize_t write(int fd, const void *buf, size_t count);
//fd:是一个文件描述符,在socket编程中即socket的文件描述符。
//buf:是一个指向要发送数据的缓冲区的指针。
//count:指定了要写入的数据的字节数。
//返回值:
//正数:write函数成功地将数据写入socket的发送缓冲区,它将返回写入的字节数
//-1 : 写入数据时遇到错误

第六步 :通过close关闭socket

int close(int fd);
//fd:fd是一个整数类型的文件描述符,它标识了需要关闭的socket
//返回值:
//0:代表close函数调用成功
//-1:代表调用失败

2.1.2 服务端代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include <unistd.h>
#include <ctype.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#define DEFAULT_PORT 10997  //默认端口
#define MAXLINE 4096
int main(int argc, char** argv)
{
    //第一步
    int servicesocket_fd;
    if(servicesocket_fd = socket(AF_INET,SOCK_STREAM,0) == -1)//创建socket
    {
        printf("service create socket error\n");
    }
  
    //第二步
    //1.初始化serviceaddr,然后bind
    struct sockaddr_in serviceaddr;
    memset(&serviceaddr,0,sizeof(serviceaddr));

    serviceaddr.sin_family = AF_INET;//设置协议族
   
    //serviceaddr.sin_addr.s_addr = inet_addr("172.17.0.1");//设置ip地址。inet_addr作用是用于ipv4的转换。
    serviceaddr.sin_addr.s_addr = htonl(INADDR_ANY);//获取本机的有效ip地址
    serviceaddr.sin_port = htons(DEFAULT_PORT);//设置端口号,htons的作用是将整形变量从主机字节顺序转化为网络字节顺序

    if(bind(servicesocket_fd,(struct sockaddr*)&serviceaddr,sizeof(serviceaddr))== -1){
        printf("socket bind fail\n");
        return 0;

    }

    //第三步
    if(listen(servicesocket_fd,4)== -1){
        printf("socket listen fail\n");
        return 0;
    }
    
    struct sockaddr_in clientaddr;
    socklen_t clientaddrsize = sizeof(clientaddr);
    int clientsocket_fd =accept(servicesocket_fd,(struct sockaddr*)&clientaddr,&clientaddrsize);

    if(clientsocket_fd ==-1){
        printf("accept fail\n");
        return 0;
    }




    char readbuf[1024];
    char writebuf[1024] = {'s','e','w','r'};
    while(1){//死循环的读取客户端发送的数据
        int n = read(clientsocket_fd,readbuf,sizeof(readbuf));
        if(n>0){
            printf("readbuf =%s",readbuf);
            write(clientsocket_fd,writebuf,sizeof(writebuf));
            continue;
        }
    }

    close(servicesocket_fd);
    close(clientsocket_fd);

    

    return 0;
}

2.2 客户端

2.2.1 步骤

第一步:创建客户端socket

    int socket(int domain, int type, int protocol);
    // 创建一个套接字
    // domain:协议族,AF_INET、AF_INET6、AF_UNIX,AF_INET代表是ipV4,AF_INET6代表是ipv6
    // type:socket类型。SOCK_STREAM、SOCK_DGRAM
    // protocol:协议,0 使用type对应的默认协议
    // 返回值:
    // 成功: 新套接字所对应文件描述符
    // 失败: -1 errno

第二步:连接服务端

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    // 使用现有的 socket 与服务器socket建立连接
    // sockfd: clientsocket 函数返回值

    // struct sockaddr_in srv_addr; // 服务器地址结构
    // srv_addr.sin_family = AF_INET;
    // srv_addr.sin_port = 9527 	// 跟服务器bind时设定的 port 完全一致。
    // inet_pton(AF_INET, "服务器的IP地址",&srv_adrr.sin_addr.s_addr);
    // addr:传入参数,服务器的地址结构
    // addrlen:服务器的地址结构的大小

    // 返回值:
    // 成功:0
    // 失败:-1 errno
    // 如果不使用bind绑定客户端地址结构, 采用"隐式绑定"。

第三步:通过write向服务端发送数据

ssize_t write(int fd, const void *buf, size_t count);
//fd:是一个文件描述符,在socket编程中即socket的文件描述符。
//buf:是一个指向要发送数据的缓冲区的指针。
//count:指定了要写入的数据的字节数。
//返回值:
//正数:write函数成功地将数据写入socket的发送缓冲区,它将返回写入的字节数
//-1 : 写入数据时遇到错误

第四步:通过read读取来自服务端的数据

ssize_t read(int fd, void *buf, size_t count);
//fd:是一个文件描述符,在socket编程中就是socket的文件描述符。
//buf:是一个指向缓冲区的指针,数据将从socket中读取到这个缓冲区中。
//count:指定了最多读取的字节数。
//返回值:
//-1: 表示读取过程中出现了错误
// 0: 如果read函数返回0,这通常意味着对端已经关闭了连接,或者是一个非阻塞的socket在没有数据可读时
//   返回的结果。对于TCP连接,这通常意味着对端已经发送了一个FIN包来关闭连接,并且本地socket也接收
//   到了这个FIN包。
//正整数:如果read函数成功读取了一些数据,它将返回读取到的字节数。

第五步:关闭socket,释放资源

int close(int fd);
//fd:fd是一个整数类型的文件描述符,它标识了需要关闭的socket
//返回值:
//0:代表close函数调用成功
//-1:代表调用失败

2.2.2 客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DEFAULT_PORT 10997
int main(int argc, char** argv)
{

    int clientsocket_fd;
    if (clientsocket_fd = socket(AF_INET, SOCK_STREAM, 0) == -1)
    {
        printf("create clientsocket_fd fail\n");
        return 0;
    }

    // 第二步:
    // 1.初始化serviceaddr
    struct sockaddr_in serviceaddr;
    serviceaddr.sin_family = AF_INET;                     // 设置协议族
    serviceaddr.sin_port = htons(DEFAULT_PORT);           // 设置端口号,htons的作用是将整形变量从主机字节顺序转化为网络字节顺序
    //serviceaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置ip地址。inet_addr作用是用于ipv4的转换。
    serviceaddr.sin_addr.s_addr = htonl(INADDR_ANY);      //获取本机的有效ip地址

    if (connect(clientsocket_fd, (struct sockaddr *)&serviceaddr, sizeof(serviceaddr)) == -1)
    {
        printf("client connect fail\n");
        return 0;
    }

    char clientwritebuf[1024] = {'c','l','w','t','\n'};
    int wr =write(clientsocket_fd,clientwritebuf,sizeof(clientwritebuf));

    char clientreadbuf[1024];

    while(1){
    int cre = read(clientsocket_fd,clientreadbuf,sizeof(clientreadbuf));
    if (cre>0){

        printf("clientreadbuf = %s\n",clientreadbuf);

        break;


       }
    }
	close(clientsocket_fd);


    return 0;
}