Linux 网络编程(socket)

1.网络编程概述

之前学习的进程间通讯(管道、消息队列、共享内存,信号、信号量)都是依赖于内核,这也就使得它们进行单机通讯,不能多机通讯是它们得缺陷,对于我们的应用场景来说很多不是单机的,所以之前学习的进程间通讯是不够的,这里就需要用到网络实现多机通讯(如Linux和Android或者Ios等设备得网络通讯)。网络编程关心得:

  • 1、IP地址和端口号;
  • 2、数据:协议(http、udp/tcp)即数据格式

这里我们学习得是socket(套接字)网络编程,用得协议是tcp/udp(如下图)
tcp:面向连接得,A跟B打电话,特点是双方都能确定接通,tcp应用场景是精细操作,如:传指令,控制等
udp:面向报文得,A跟B发短信,特点是不关心对方能否收得到,连接不可靠得,udp应用场景是数据量大得,如:视频聊天,在数据交互丢几个字节是没问题得。
在这里插入图片描述

以上是对下图得描述:
在这里插入图片描述
下图是针对端口号作用具体的描述:(应用开发的端口号选择5000以上)
在这里插入图片描述

2.字节序

什么是字节序:

  • 字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
    常见序
    1. Little endian:将低序字节存储在起始地址(小端字节序)
    2. Big endian:将高序字节存储在起始地址(大端字节序)
    网络字节序 = 大端字节序
    x86系列cpu都是little-endian的字节序(小端字节序)

所以电脑如果需要通过网络上传数据就先用函数转成大端字节序,然后接收端接收再由函数转为小端字节序,不然发送数据的化上传到网络数据因为字节序的不同就会坏了的。

下图数对上述的图片:
在这里插入图片描述

3.socket编程步骤

在这里插入图片描述

4.Linux提供的API简析

在这里插入图片描述
在这里插入图片描述
地址转换API在实际编程中我们会用到,人眼看到的地址是192.168.1.123,但是这个丢到网络上面网络不认识了,所以需要这个函数。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.socket服务端代码实现一

在这里插入图片描述
下面对服务端的代码进行简单编程:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>

int main()
{
        int s_fd;

        //1. socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
        if(s_fd == -1);{
                perror("socket");
                exit(-1);
        }

        //2. bind
        struct sockaddr_in s_addr;//配置端口和网络等的结构体。
        s_addr.sin_family = AF_INET;//配置协议
        s_addr.sin_port = htons(8989);//配置端口号需要注意字节序所以用转字节序的函数
        inet_aton("127.0.0.1",&s_addr.sin_addr);//配置IP地址用了地址转换API;需要注意的是第二个参数结构体不用写的太进去。

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//绑定信息,第二个参数需要把现在优化的结构体强转为之前的结构体类型。

        //3. listen
        listen(s_fd,10);//监听10个连接
        //4. accept
        int c_fd = accept(s_fd,NULL,NULL);//如果没有连接的话会阻塞卡这
        //5. read

        //6. write
        printf("connect\n");
		while(1)return 0;
}

以上代码只是简单的能够连接其他设备,运行结果如下服务端是基于tcp协议,客户端的telnet正好也是tcp协议:
连接前windows命令窗口输入:
在这里插入图片描述

连接成功后:
在这里插入图片描述

6.socket服务端代码实现二

这里也是对socket服务端代码(sever.c)编写,比上面的代码多的是accept可以接收客户端的地址信息,(但在printf的时候把网络地址格式的IP转为字符串形式的函数注意);以及对客户端的读写操作,都是通过c_fd的;因为socket的描述符需要与其他的客户端连接。

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

int main()
{
        int s_fd;
        int nread;
        char readBuf[128];
        char *msg ="I get Your message\n";

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1. socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }

        //2. bind
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(8988);
        inet_aton("192.168.0.102",&s_addr.sin_addr);

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3. listen
        listen(s_fd,10);
        //4. accept
        int clen = sizeof(struct sockaddr_in);
        int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);//第二个参数是接收客户端的IP地址,所以结构体和bind第二个参数一样;第三个参数是计算这个结构体的大小,但是是个指针所以需要用clen转换一下。
        if(c_fd == -1){
                perror("accept");
        }
        printf("connect\n");

        printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
        //5. read
        nread = read(c_fd,readBuf,128);//读客户端数据
        if(nread == -1){
                perror("accept");
        }else{
                printf("get message:%d,%s\n",nread,readBuf);
        }
        //6. write
        write(c_fd,msg,strlen(msg));//给客户端写消息。


        return 0;
}                                                                                

运行结果如图:
在这里插入图片描述

7.socket客户端代码实现

这里我们写一下客户端(client.c)代码

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

int main()
{
        int c_fd;
        int n_read;
        char readBuf[128];
        char *msg ="msg from client\n";
        struct sockaddr_in c_addr;

        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1. socket
        c_fd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
        if(c_fd == -1){
                perror("socket");
                exit(-1);
        }

        //2. connect 
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(8988);
        inet_aton("192.168.0.100",&c_addr.sin_addr);

        if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in)) == -1){//连接服务端,这里的第二个参数和bind的第二个参数一样,所以客户端配置连接的参数在这第二个结构体里,然后需要强转函数要求的结构体那样的格式
                perror("connect");
                exit(-1);
        }

        //3. send
        write(c_fd,msg,strlen(msg));

        //4. read 
        n_read = read(c_fd,readBuf,128);
        if(n_read == -1){
                perror("read");
        }else{
                printf("get message from server:%d,%s\n",n_read,readBuf);
        }


        return 0;
}

自己手动写的代码于服务端的交互结果如下图:
在这里插入图片描述

8.实现双方聊天

上图的运行结果是服务端接收到客户端就直接退出了,有些残疾,这里我们做一个增强的服务端,以及修改客户端能够不断的给服务端发消息的代码(client1.c)。

服务端代码:

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

int main(int argc,char **argv)
{
        int s_fd;
        int c_fd;
        int nread;
        char readBuf[128];
        //      char *msg ="I get Your message\n";
        char msg[128] = {0};

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        if(argc != 3){
                printf("param is not good");
        }

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1. socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);//创建socket
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }
       //2. bind
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//对套接字的配置

        //3. listen//接听
        listen(s_fd,10);
        //4. accept
        int clen = sizeof(struct sockaddr_in);
        while(1){

                int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);//等待客户端的接入
                if(c_fd == -1){
                        perror("accept");
                }
                printf("connect\n");

                printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
                if(fork() == 0){//当连接新的客户端创建子进程,被创建的子进程去干两件事,一件是对客户端的写,一件是对客户端数据的读,所以这个子进程做两件是需要再创建一个子进程(或者创建线程做事也可以)去做事情。
                        
                        if(fork() == 0){//被创建去对客户端写的进程
                                while(1){
                                        memset(msg,0,sizeof(msg));
                                        printf("sever input:");
                                        gets(msg);
                                        write(c_fd,msg,strlen(msg));
                                }
                        }

                       while(1){//对客户端读取数据
                                memset(readBuf,0,sizeof(readBuf));
                                nread = read(c_fd,readBuf,128);
                                if(nread == -1){
                                        perror("accept");
                                }else{
                                        printf("\nget message:%d,%s\n",nread,readBuf);
                                }
                        }
                }


        }

        return 0;
}

客户端代码(client.c):

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

int main(int argc,char **argv)
{
        int c_fd;
        int n_read;
        char readBuf[128];
        //      char *msg ="msg from client\n";
        char msg[128] = {0};
        struct sockaddr_in c_addr;

        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1. socket
        c_fd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
        if(c_fd == -1){
                perror("socket");
                exit(-1);
        }

        //2. connect 
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);

        if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in)) == -1){//连接服务端
                perror("connect");
                exit(-1);
        }
               while(1){

                if(fork() == 0){//客户端创建一个对服务端写的进程
                        while(1){
                                memset(msg,0,sizeof(msg));
                                printf("input:");
                                gets(msg);
                                write(c_fd,msg,strlen(msg));
                        }
                }

                while(1)
                        memset(readBuf,0,128);
                        n_read = read(c_fd,readBuf,128);//不断的读服务端发的数据
                        if(n_read == -1){
                                perror("read");
                        }else{
                                printf("get message from server:%d,%s\n",n_read,readBuf);
                        }

                }
        }
        return 0;
}

运行结果如下图,(存在一些小bug,这个仅供双方客户端和服务端的通讯,如果有第二个客户端接入的话服务端发送的数据是随机发给其中一个的子进程连接客户端的,原因就是在键盘输入后按回车,子进程在争夺资源,不知道哪个子进程抢到了;客户端发的数据,服务端都可以接收到,都是没问题的,服务端发客户端就不能指定发了;下图是一个客户端和服务端的socket通讯)
在这里插入图片描述

9.多方消息收发

以上是回复客户端手动回复,但是如果有多个客户端的话,由于进程间的资源竞争就不确定会发送到哪个进程连接的客户端了,所以这里修改代码,进程自动回复给客户端。
以下是对服务端代码的修改:

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

int main(int argc,char **argv)
{
        int s_fd;
        int c_fd;
        int nread;
        char readBuf[128];
        //      char *msg ="I get Your message\n";
        char msg[128] = {0};

        int mark = 0;
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        if(argc != 3){
                printf("param is not good");
        }

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        //1. socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }
       //2. bind
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);

        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

        //3. listen
        listen(s_fd,10);
        //4. accept
        int clen = sizeof(struct sockaddr_in);
        while(1){

                int c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
                if(c_fd == -1){
                        perror("accept");
                }
                printf("connect\n");

                mark++;//做标记是第几个客户端的接入。
                printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
                if(fork() == 0){
                        if(fork() == 0){
                                while(1){//自动回复客户端,目前的只是储备不足以手动发送客户端消息
                                        sprintf(msg,"welcome No.%d client",mark);
                                        write(c_fd,msg,strlen(msg));
                                        sleep(3);
                                }
                        }

                       while(1){
                                memset(readBuf,0,sizeof(readBuf));
                                nread = read(c_fd,readBuf,128);
                                if(nread == -1){
                                        perror("accept");
                                }else{
                                        printf("\nget message:%d,%s\n",nread,readBuf);
                                }
                        }
                }


        }

        return 0;
}


在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值