第四章 Linux网络编程(2)

一、socket地址

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

二、IP地址转换函数

在这里插入图片描述
在这里插入图片描述

/*
    #include <arpa/inet.h>
    // p:点分十进制的IP字符串,n:表示network,网络字节序的整数
    int inet_pton(int af, const char *src, void *dst);
        af:地址族: AF_INET  AF_INET6
        src:需要转换的点分十进制的IP字符串
        dst:转换后的结果保存在这个里面

    // 将网络字节序的整数,转换成点分十进制的IP地址字符串
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
        af:地址族: AF_INET  AF_INET6
        src: 要转换的ip的整数的地址
        dst: 转换成IP地址字符串保存的地方
        size:第三个参数的大小(数组的大小)
        返回值:返回转换后的数据的地址(字符串),和 dst 是一样的

*/

#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
int main(){

    char buf[] = "192.168.1.14";

    unsigned int num;
    inet_pton(AF_INET,buf,&num);//将点分十进制的ip字符串转换成网络字节序的整数

    //结果保存到num里面了
    //将num里面的东西一个一个字节打印出来看看
    //转换为char型,
    unsigned char *p = (unsigned char*)&num;

    printf("%d ,%d ,%d ,%d\n",*p,*(p+1),*(p+2),*(p+3));
    //printf("p : %s\n",p);

    printf("====================\n");

    //将网络字节序的IP整数转换成点分十进制的IP字符串
    char ip[16] = "";
    
    
    const char *str = inet_ntop(AF_INET,&num,ip,sizeof(ip));
    //printf("%d ,%d ,%d ,%d\n",*p,*(p+1),*(p+2),*(p+3));
    printf("str : %s\n",str);


    return 0;
}

在这里插入图片描述

三、TCP通信流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、socket函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、TCP通信实现(服务器端)

// TCP通信的服务器端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

int main(){

    //1、创建socket(用于监听) 成功返回文件描述符,失败返回-1
    int sockfd = socket(AF_INET,SOCK_STREAM,0);  
    if (sockfd == -1){
        perror("socket");
        return -1;
    }

    //2、绑定ip 和端口 成功返回0失败返回-1
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    //192.168.78.161是字符串,而saddr.sin_addr.s_addr传入的值只能为整数,所以要使用inet_pton将字符串形式的ip地址转换为整数型的网络字节序
    //unsigned int *buf;
    //inet_pton(AF_INET,"192.168.78.161",&buf);
    //saddr.sin_addr.s_addr = &buf;
    //服务端的偷懒写法,INADDR_ANY就是0,写0代表任意地址,表示任意ip都能访问到我们
    saddr.sin_addr.s_addr = INADDR_ANY;
    //将主机端口号为9999的转换为网络字节序   主机字节序->网络字节序
    unsigned short sprot = htons(9999);
    saddr.sin_port = sprot;
    // saddr的类型是sockaddr_in,而bind里面要传入sockaddr类型的,所以要强制转换一下
    int ret = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(ret == -1){
        perror("bind");
        return -1;
    }

    // 3、监听   成功返回0失败返回-1
    ret = listen(sockfd,8);
    if(ret == -1){
        perror("listen");
        return -1;
    }

    // 4、接收客户端连接  成功返回文件描述符,失败返回-1   accept里面的第三个参数长度,该数据类型是指针,和bind不一样
    // 阻塞函数,如果没有客户端连接,会一直阻塞在这
    //clientaddr 是已经连接进来的客户端的信息
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    int accfd = accept(sockfd,(struct sockaddr *)&clientaddr,&len);
    if(accfd == -1){
        perror("accept");
        return -1;
    }

    //程序走到这里,说明已经保存了客户端信息,我们打印出来看看
    //上面是网络字节序,打印的时候要转换成主机字节序
    //xxx.xxx.xxx.xxx 3*4+3=15最后还有一个\0 一共16个字节
    char clientIP[16];
    inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,clientIP,sizeof(clientIP));
    //不要忘记网络字节序转主机字节序
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s , port is %d \n",clientIP,clientPort);

    //5、通信
    //获取客户端的数据
    char recvBuf[1024] = {0};
    while(1){

        int lens = read(accfd,recvBuf,sizeof(recvBuf));
        if(lens == -1){
            perror("read");
            return -1;
        }else if (lens >0){
            //读到了数据
            //将读到的数据输出出来
            printf("recv client data : %s \n",recvBuf);
        }else if(lens == 0){
            //表示客户端断开连接
            printf("客户端端口连接。。。。");
            break;
        }

        //给客户端发送数据
        char *data = "hello, i am server";
        write(accfd,data,strlen(data));

        //其实服务端可以不用sleep,因为客户端睡眠1秒才发送,这1秒内服务器端处于阻塞状态,没有数据可读
        //sleep(1);
    }
    

    //关闭文件描述符
    close(accfd);
    close(sockfd);

    return 0;

}

六、TCP通信实现(客户端)

//TCP通信 客户端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

int main(){
    //1、创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }

    //2、连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET,"192.168.78.161",&serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    if(ret == -1){
        perror("connect");
        return -1;
    }

    //3、通信
    char recvBuf[1024]={0};
    while(1){

        char *data = "hello , i am client";
        //给服务器端发送数据
        write(sockfd,data,strlen(data));

        sleep(1);

        //读数据
        int len = read(sockfd,recvBuf,sizeof(recvBuf));
        if(len == -1){
            perror("read");
            return -1;
        }else if(len > 0){
            //读到了数据
            printf("recv server data : %s\n",recvBuf);
        }else if(len == 0){
            //服务器端断开连接
            printf("server closed.....");
            break;
        }


    }
    

    //关闭连接
    close(sockfd);




    return 0;
}

七、 TCP三次握手

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

八、 滑动窗口

在这里插入图片描述
窗口理解为缓冲区的大小
滑动窗口的大小会随着发送数据和接收数据而变化
通信双方都有发送数据的缓冲区和接收数据的缓冲区

服务器:
	发送缓冲区 (发送缓冲区的窗口)
	接收缓冲区 (接收缓冲区的窗口)

客户端:
	发送缓冲区 (发送缓冲区的窗口)
	接收缓冲区 (接收缓冲区的窗口)

在这里插入图片描述

发送方的缓冲区:
	白色格子:空闲的空间
	灰色格子:数据已经被发送出去了,但是还没有被接收
	紫色格子:还没有发送出去的数据
	
接收方的缓冲区:
	白色格子:空闲的空间
	紫色格子:已经接收到的数据

在这里插入图片描述

mss : Maximum Segment Size(一条数据的最大的数据量)
win : 滑动窗口
1、客户端向服务器发起连接,客户端的滑动窗口是4096,一次发送的最大数据量是1460
2、服务器接收连接请求,告诉客户端服务器窗口大小是6144,一次发送的最大数据量是1024
3、第三次握手
44-9客户端连续给服务器发送了6k的数据,每次发送1k
5、第10次,服务器告诉客户端:发送的6k数据已经接收到,存储在缓冲区中,缓冲区数据已经处理了2k,窗口大小是2k
6、第11次,服务器告诉客户端:发送的6k数据已经接收到,存储在缓冲区中,缓冲区数据已经处理了4k,窗口大小是4k
7、第12次,客户端给服务器发送了1k的数据

剩下的就是四次挥手的内容

8、第13次,客户端主动请求和服务器断开连接,并且给服务器发送了1k的数据
9、第14次,服务器回复ACK 8194, a:同意断开连接的请求 b:告诉客户端已经接收到刚才发的2k的数据 c:滑动窗口2k
10、第1516次,通知客户端滑动窗口的大小
11、第17次,第三次挥手,服务器端给客户端发送FIN,请求断开连接
12、第18次,第四次挥手,客户端同意了服务器端的断开请求

九、 TCP四次握手

客户端和服务端建立连接的时候,需要三次握手操作,来确保通信双方都是正常的。

tcp四次挥手发生在断开连接的时候,在程序中当调用了close()会使用TCP协议进行四次挥手

在这里插入图片描述

十、 TCP通信并发(多进程)

要实现TCP通信服务器处理并发的任务,使用多线程或者多进程来解决

思路:
	1、一个父进程,多个子进程
	2、父进程负责等待并接受客户端的连接
	3、子进程:完成通信,接收一个客户端连接,就创建一个子进程用于通信。

server_process.c

//TCP通信 客户端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

int main(){
    //1、创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }

    //2、连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET,"192.168.78.165",&serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    if(ret == -1){
        perror("connect");
        return -1;
    }

    //3、通信
    char recvBuf[1024]={0};
    int i = 0;
    while(1){

        //char *data = "hello , i am client";
        sprintf(recvBuf,"data : %d \n",i++);
        //给服务器端发送数据
        //strlen +1 是为了把结束符 \0 加进去,不然会出错
        write(sockfd,recvBuf,strlen(recvBuf)+1);

        //读数据
        int len = read(sockfd,recvBuf,sizeof(recvBuf));
        if(len == -1){
            perror("read");
            return -1;
        }else if(len > 0){
            //读到了数据
            printf("recv server data : %s\n",recvBuf);
        }else if(len == 0){
            //服务器端断开连接
            printf("server closed.....");
            break;
        }
        sleep(1);

    }
    

    //关闭连接
    close(sockfd);




    return 0;
}

client_process.c

//TCP通信 客户端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

int main(){
    //1、创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }

    //2、连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET,"192.168.78.165",&serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    if(ret == -1){
        perror("connect");
        return -1;
    }

    //3、通信
    char recvBuf[1024]={0};
    int i = 0;
    while(1){

        //char *data = "hello , i am client";
        sprintf(recvBuf,"data : %d \n",i++);
        //给服务器端发送数据
        //strlen +1 是为了把结束符 \0 加进去,不然会出错
        write(sockfd,recvBuf,strlen(recvBuf)+1);

        //读数据
        int len = read(sockfd,recvBuf,sizeof(recvBuf));
        if(len == -1){
            perror("read");
            return -1;
        }else if(len > 0){
            //读到了数据
            printf("recv server data : %s\n",recvBuf);
        }else if(len == 0){
            //服务器端断开连接
            printf("server closed.....");
            break;
        }
        sleep(1);

    }
    

    //关闭连接
    close(sockfd);




    return 0;
}

服务器端效果图

在这里插入图片描述

十一、TCP通信并发(多线程)

#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<signal.h>
#include<wait.h>
#include<errno.h>
#include<pthread.h>

struct sockInfo{
    int fd; //通信的文件描述符
    pthread_t tid; //线程号
    struct sockaddr_in addr;
};

struct sockInfo sockinfos[128];

void *working(void * arg){
    //子线程和客户端通信 cfd,客户端的信息,线程号

    //获取客户端的信息
    struct sockInfo *pinfo = (struct sockInfo *)arg;
    //定义一个数组保存客户端的信息&accaddr.sin_addr.s_addr
    char cliIp[16];
    inet_ntop(AF_INET,&pinfo->addr.sin_addr.s_addr,cliIp,sizeof(cliIp));
    unsigned short cliPort = ntohs(pinfo->addr.sin_port);
    printf("client ip is : %s, port is : %d\n",cliIp,cliPort);

    //接收客户端发来的信息
    char resvBuf[1024];
    while(1){
        
        int readlens = read(pinfo->fd,&resvBuf,sizeof(resvBuf));
        if(readlens == -1){
            perror("read");
            //return -1;
            exit(-1);
        }else if(readlens == 0){
            //客户端断开了连接
            printf("client closed.....\n");
            break;
        }else if(readlens > 0){
            printf("resv client : %s\n",resvBuf);
        }
        
        //把读出来的数据再写回去,回射服务器
        write(pinfo->fd,resvBuf,strlen(resvBuf)+1);
    }
    close(pinfo->fd);
    return NULL;//退出当前子进程

}

int main(){

    //创建socket
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1){
        perror("socket");
        return -1;
    }

    //绑定ip和端口
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY;
    //socklen_t slen = sizeof(saddr);
    int ret = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(ret == -1){
        perror("bind");
        return -1;
    }

    //监听
    ret = listen(sockfd,128);
    if(ret == -1){
        perror("listen");
        return -1;
    }

    //初始化数据
    int max = sizeof(sockinfos) / sizeof(sockinfos[0]);
    for(int i = 0; i<max;i++){
        bzero(&sockinfos[i],sizeof(sockinfos[i]));
        sockinfos[i].fd = -1;
        sockinfos[i].tid = -1;
    }


    //循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信
    while(1){

        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);

        //接受连接
        int cfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);

        struct sockInfo *pinfo;
        for(int i =0;i<max;++i){
            //从这个数组中找到一个可以用的sockInfo元素
            if(sockinfos[i].fd == -1){
                pinfo = &sockinfos[i];
                break;
            }
            if( i == max -1){
                sleep(1);
                i--;
            }
        }
        
        pinfo -> fd = cfd;
        memcpy(&pinfo->addr,&cliaddr,len);

        //创建子线程
        //pthread_t tid;
        pthread_create(&pinfo->tid,NULL,working,pinfo);


        //回收子线程资源
        //不能使用pthread_join 因为join是阻塞的,其他客户端进不来
        pthread_detach(pinfo->tid);
    }
    close(sockfd);

    return 0;
}

client和前面一样

十二、TCP状态转换

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

十三、半关闭、端口复用

半关闭在上面

在这里插入图片描述

setsockopt不仅仅能设置端口复用,还能设置套接字的属性
参数:
	sockfd:要操作的文件描述符
	level:级别 - SOL_SOCKET(端口复用的级别)
	optname:选项的名称
		SO_REUSEADDR
		SO_REUSEPORT
	optval:端口复用的值(整形)
		1:可以复用
		0:不可以复用
	optlen:optval参数的大小

端口复用,设置的时机是在服务器绑定端口之前
setsockopt()
bind()
查看网络相关信息的命令
netstat
	参数:
		-a 所有的socket
		-p 显示正在使用socket的程序的名称
		-n 直接使用IP地址,而不通过域名服务器

在这里插入图片描述

#include <stdio.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {

    // 创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);

    if(lfd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons(9999);
    
    //int optval = 1;
    //setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    int optval = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));

    // 绑定
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if(ret == -1) {
        perror("bind");
        return -1;
    }

    // 监听
    ret = listen(lfd, 8);
    if(ret == -1) {
        perror("listen");
        return -1;
    }

    // 接收客户端连接
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
    if(cfd == -1) {
        perror("accpet");
        return -1;
    }

    // 获取客户端信息
    char cliIp[16];
    inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
    unsigned short cliPort = ntohs(cliaddr.sin_port);

    // 输出客户端的信息
    printf("client's ip is %s, and port is %d\n", cliIp, cliPort );

    // 接收客户端发来的数据
    char recvBuf[1024] = {0};
    while(1) {
        int len = recv(cfd, recvBuf, sizeof(recvBuf), 0);
        if(len == -1) {
            perror("recv");
            return -1;
        } else if(len == 0) {
            printf("客户端已经断开连接...\n");
            break;
        } else if(len > 0) {
            printf("read buf = %s\n", recvBuf);
        }

        // 小写转大写
        for(int i = 0; i < len; ++i) {
            recvBuf[i] = toupper(recvBuf[i]);
        }

        printf("after buf = %s\n", recvBuf);

        // 大写字符串发给客户端
        ret = send(cfd, recvBuf, strlen(recvBuf) + 1, 0);
        if(ret == -1) {
            perror("send");
            return -1;
        }
    }
    
    close(cfd);
    close(lfd);

    return 0;
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值