【回眸】技术干货——Linux 内核 (十八) 之 网络编程

前言

努力赶紧把Linux内核的内容更新完。

网络编程

协议的部分已经很成熟,只需要调用即可。

进程间通讯无法进行多机通信,网络通讯则解决了这一缺陷。

TCP/UDP协议对比

(1)TCP 面向连接(如打电话要先拨号建立连接)提供可靠的服务。可靠指:通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;

UDP 是无连接的,即发送数据之前不需要建立连接,UDP 尽最大努力交付,即不保证可靠交付。

(2)UDP 具有较好的实时性,工作效率比 TCP 高,适用于对高速传输和实时性有较高的通信或广播通信。

(3)TCP 连接只能是一对一的,UDP 支持一对一,一对多,多对一和多对多的交互通信。

解释:打电话就是TCP协议,每次只能给一个人打电话。UDP就像是QQ发送信息,可以一对多的给别人发消息。

(4)TCP是面向字节流,把数据看成一连串无结构的字节流,UDP 是面向报文的一次交付一个完整的报文,报文不可分割,报文是 UDP 数据报处理的最小单位。

(5)TCP 和 UDP 都可以检查错误,但只有 TCP 可以纠正错误,因为它同时具有拥塞和流量控制。

(6)TCP只能进行点对点的数据传输,不支持多播和广播传输方式。

端口号的作用

端口,在网络技术中,是指计算机或网络设备上的一个特定服务访问点。它是一个逻辑上的概念,用于标识不同类型的网络服务。每一个端口都对应一个唯一的数字,这个数字范围通常是0到65535。这些数字被分为知名的、注册的、动态的和私有的几个区间。一般做应用是用5000以上的端口号。

字节序

小端字节序:低序字节存储在最低位

大端字节序:高序字节存储在最低位(socket字节序)

socket编程的流程我整理成一张表格。

socket编程涉及到的Linux提供的API

1.创建套接字(确定协议)

#include <sys/types.h>        
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

int domain//协议族。例如AF_INET、AF_INET6即(ipv4,ipv6)等。
	AF_INET  选择ipv4协议族
	AF_INET6 选择ipv6协议族
    
int type//指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM等。
	SOCK_STREAM 流式的协议栈,代表协议有TCP等。
	SOCK_DGRAM 数据报文的协议栈,代表协议有UDP等。
    
int protocol//最终确定的协议,设为0时,会自动选择type类型对应的默认协议。

 2.确定地址

#include <sys/types.h>        
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

int sockfd//socket的文件描述符。
    
const struct sockaddr *addr//一个结构体指针,指向sockfd要绑定什么样的协议的首地址。
    
socklen_t addrlen//地址的长度。

//通用套接字地址
    struct sockaddr {
      unsigned short sa_family;   //协议族 
      char sa_data[14];  //IP+端口号
    };

//IPv4套接字地址
    struct sockaddr_in { 
      unsigned short sin_family;   //协议族
      unsigned short sin_port;  //端口号
      struct in_addr sin_addr;  //IP地址的结构体
      unsigned char sin_zero[8];//填充,没实际意义,作用是对齐
    };
     
    struct in_addr {
      unsigned int  s_addr;
    };


字节序转换API

#include <netinet/in.h>

uint16_t htons(uint16_t host16bitvalue);//返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);//返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue);//返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue); //返回主机字节序的值

IP地址转换API

1.IP字符转换为网络能识别的格式:inet_aton();【ipv4】

#include<sys/scoket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int inet_aton(const char * cp,struct in_addr *inp);

2.网络格式的IP地址转化为字符串格式inet_nota();【ipv4】

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

char * inet_ntoa(struct in_addr in);

 3.监听

#include <sys/types.h>        
#include <sys/socket.h>
int listen(int sockfd, int backlog);

int sockfd//要监听的socket对应的文件描述符。

int backlog//监听的数量最多可以等待几个客户端,比如设置为5,那么连接第6个客户端连接服务器时,就连接不进来了。

4.连接

#include <sys/types.h>    
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

int sockfd//要接收的socket的文件描述符。
    
struct sockaddr *addr//用于存储客户端的ip地址以及端口

socklen_t *addrlen//用来存储客户端协议地址的长度的指针。是一个value-result argument,所以我们需要将他初始化为比struct sockaddr *addr长的值,不然就会发生参数错误。

5.数据收发

读函数原型

#include <unistd.h>
 
ssize_t read(int fd, void *buf, size_t count);

写函数原型

#include <unistd.h>
 
ssize_t write(int fd, const void *buf, size_t count);

 读函数原型2

ssize_t recv(int socket, void *buf, size_t len, int flags)
参数一:指定接收端套接字描述符;
参数二:指向一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
参数三:指明buf的长度;
参数四:一般置为0;
返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是返回接收数据的长度。

写函数原型2

ssize_t send(int socket, const void *buf, size_t len, int flags);  
参数一:指定发送端套接字描述符;
参数二:指明一个存放应用程序要发送数据的缓冲区;
参数三:指明实际要发送的数据的字节数;
参数四:一般置0;
返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是返回发送数据的长度。

socket编程实战

sever.c(服务端)

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

int main(){
int s_fd;
int c_fd;
int c_len;
int n_read;
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));

s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8989);
inet_aton("10.208.108.5",&s_addr.sin_addr);

//创建socket
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket\n");
exit(-1);
}
//bind地址

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

//acpet
c_len = sizeof(struct sockaddr_in);
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len);
if(c_fd == -1){

perror("accept-1\n");
}
printf("Get conneted%s\n",inet_ntoa(c_addr.sin_addr));
printf("Connet.\n");
//read
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else{
printf("get message %d,%s\n",n_read,readBuf);
}
//write
write(c_fd,msg,strlen(msg));

//close

return 0;
}

效果图展示

效果图展示

 client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>

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

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

c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(8989);
inet_aton("10.208.108.5",&c_addr.sin_addr);

//创建socket
c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
perror("socket\n");
exit(-1);
}
//connect
c_res = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));
if(c_res == -1)
{
perror("connect");
exit(-1);
}
//write
write(c_fd,msg,strlen(msg));

//read
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else{
printf("Get message from sever: %d,%s\n",n_read,readBuf);
}
//write
write(c_fd,msg,strlen(msg));

//close

return 0;
}

效果图展示

改良1版(可以传IP和端口号)

sever.c (改良1)

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.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 c_len;
int n_read;
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));

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

//创建socket
s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
perror("socket\n");
exit(-1);
}
//bind地址

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

//acpet
c_len = sizeof(struct sockaddr_in);
while(1)
{
c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len);
if(c_fd == -1){

perror("accept-1\n");
}
printf("Get conneted%s\n",inet_ntoa(c_addr.sin_addr));
printf("Connet.\n");
if(fork() == 0){
//read
n_read = read(c_fd,readBuf,128);
if (n_read == -1)
{
perror("read");
}
else{
printf("get message %d,%s\n",n_read,readBuf);
}
//write
write(c_fd,msg,strlen(msg));
break;
//close
}
}
return 0;
}

效果图展示

 sever.c(改良2)实现双方互发消息

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.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 c_len;
    int n_read;
    char readBuf[128];
    char msg[128] = {0};
    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));
    if (argc != 3)
    {
        printf("Param ERROR!\n");
        exit(-1);
    }

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

    // 创建socket
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (s_fd == -1)
    {
        perror("socket\n");
        exit(-1);
    }
    // bind地址

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

    // acpet
    c_len = sizeof(struct sockaddr_in);
    while (1)
    {
        c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &c_len);
        if (c_fd == -1)
        {

            perror("accept");
        }
        printf("Get conneted%s\n", inet_ntoa(c_addr.sin_addr));
        if (fork() == 0)
        {
            if (fork() == 0){
                while (1)
                {
                    memset(msg, 0, sizeof(msg));
                    printf("Input:\n");
                    gets(msg);
                    write(c_fd, msg, strlen(msg));
                }
            }   
            // read
            while (1)
            {
                memset(readBuf,0,sizeof(readBuf));
                n_read = read(c_fd, readBuf, 128);
                if (n_read == -1)
                {
                    perror("read");
                }
                else
                {
                    printf("Get message from client %s\n", readBuf);
                }
            }
            break;
        }
    }
    return 0;
}

 client.c(改良2)实现双方互发消息

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.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 c_len;
    int c_res;
    int n_read;
    char readBuf[128];
    char msg[128] = {0};
    struct sockaddr_in c_addr;

    memset(&c_addr, 0, sizeof(struct sockaddr_in));
    if (argc != 3)
    {
        printf("Param ERROR!\n");
        exit(-1);
    }

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

    // 创建socket
    c_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (c_fd == -1)
    {
        perror("socket\n");
        exit(-1);
    }
    // connect
    c_res = connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr));
    if (c_res == -1)
    {
        perror("connect");
        exit(-1);
    }
    while (1)
    {
        if (fork() == 0)
        {
            while (1)
            {
                printf("Please Input Message: \n");
                memset(msg, 0, strlen(msg));
                gets(msg);
                write(c_fd, msg, strlen(msg));
            }
        }
        while (1)
        {
            memset(readBuf, 0, sizeof(readBuf));
            n_read = read(c_fd, readBuf, 128);
            if (n_read == -1)
            {
                perror("read");
            }
            else
            {
                printf("Get message from sever:%s\n",readBuf);
            }
        }
        
    }

    //write(c_fd, msg, strlen(msg));

    // close

    return 0;
}

效果图展示

后记碎碎念

Linux内核是一个系列,可以点击专栏查看同系列的其他文章,希望能帮到屏幕前的每一位应届生往届生,该博文最初发表在CSDN上。

#实习生如何通过转正##Tplink求职进展汇总##tplink提前批进度交流##安克创新求职进展汇总##面试被问“你的缺点是什么?”怎么答#
应届生必学实用物联网技术 文章被收录于专栏

本专栏助应届生从物联网小白成长为企业争抢的技术人才,聚焦三大核心技术:传感器应用(环境监测)、嵌入式开发(STM32/Arduino)、通信协议(LoRa/NB-IoT/MQTT),配合10+实战项目(如智能温湿度监控系统)积累项目经验。覆盖智能硬件、工业物联网、智能家居领域岗位需求,解析企业招聘技术重点与面试题,帮电子、计算机、自动化等专业学生构建知识体系,提前锁定名企Offer!

全部评论

相关推荐

10-10 01:10
已编辑
深圳大学 测试开发
牛客26692713...:项目经历写那么多没啥用吧,挑两个最好的,其实浓缩成一页会比较好吧,背景、实习、项目,要是还有空间就再加个专业技能
投了多少份简历才上岸
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务