传统的进程间通信借助内核提供IPC机制进行,但是只能限制于本机通信,若要进行跨机通信,就要使用网络通信。

​ 网络通信的本质是借助内核提供SOCKET伪文件的机制进行通信,实际上是使用了文件描述符,因此需要使用内核提供的socketAPI函数库(在传输层层面进行)。

使用socket会建立一个socket pair,如下图, 一个文件描述符操作两个缓冲区:内核中需要维护两个缓冲区,发送端缓冲区和接收端缓冲区, 这点跟管道是不同的, 管道是两个文件描述符操作一个内核缓冲区。

1.基础知识

主机字节序和网络字节序

主机字节序列分为大端字节序和小端字节序,不同的主机采用的字节序列可能不同。大端字节序是指一个整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。 在两台使用不同字节序的主机之间传递数据时,可能会出现冲突。所以,在将数据发送到网络时规定整形数据使用大端字节序,所以也把大端字节序成为网络字节序列。对方接收到数据后,可以根据自己的字节序进行转换。

大端:手机、网络 || 小端:电脑

主机字节序列:大端/小端 || 网络字节序列:大端

大小端转换

网络传输用的是大端法,如果机器用的是小端则需要进行大小端转换,Linux 系统提供如下 4 个函数来完成主机字节序和网络字节序之间的转换:

#include <netinet/in.h>
uint32_t htonl(uint32_t hostlong); // 长整型的主机字节序转网络字节序
uint32_t ntohl(uint32_t netlong); // 长整型的网络字节序转主机字节序
uint16_t htons(uint16_t hostshort); // 短整形的主机字节序转网络字节序
uint16_t ntohs(uint16_t netshort); // 短整型的网络字节序转主机字节序

函数名的h表示主机host,n表示网络network,s表示short,l表示long,如果本来不需要转换函数内部就不会转换。

通用socket地址结构

struct sockaddr–存放IP地址,缺点是赋值不方便

struct sockaddr{sa_family_t sa_family;char sa_data[14];
}

struct sockaddr_in----sockaddr的升级

struct sockaddr_in{sa_family_t sin_family;// address family: AF_INETin_port_t sin_port;//port in network byte orderstruct in_addr sin_addr;//internet address
}
/* Internet address. */
struct in_addr{uint32_t s_addr;
}

sa_family 成员是地址族类型(sa_family_t) 的变量。地址族类型通常与协议族类型对应。常见的协议族和对应的地址族如下图所示:

专用socket地址结构

socket 网络编程接口中表示 socket 地址的是结构体 sockaddr,其定义如下:

#include <bits/socket.h>
struct sockaddr
{sa_family_t sa_family;//协议族
char sa_data[14];//数据,没有给出IP地址,就是给了这么一块儿空间,起了一个占位
的作用.
};

sa_family 成员是地址族类型(sa_family_t) 的变量。地址族类型通常与协议族类型对应。常见的协议族和对应的地址族如下图所示:

专用socket地址结构

TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用 socket 地址结构体,它们分别用于 IPV4 和 IPV6:

//sin_family: 地址族 AF_INET
//sin_port: 端口号,需要用网络字节序表示
//sin_addr: IPV4 地址结构: s_addr 以网络字节序表示 IPV4 地址
struct in_addr
{u_int32_t s_addr;//无符号的32位的整型,存放IP地址;
};
//tcp协议族
struct sockaddr_in
{sa_family_t sin_family;//地址族,就是sin_family: 地址族 AF_INET
u_int16_t sin_port;//端口,16位的端口
struct in_addr sin_addr;//一个结构体,只有一个成员,是无符号的32位的整型,
存放IP地址;(IPV4的地址就是32位)
//其实后面还有占位的,只是我们不用它,所以就没有写;
};
//tcp协议族就主要有三个:地址族,端口号,IP地址
//IP协议族
struct in6_addr
{unsigned char sa_addr[16]; // IPV6 地址,要用网络字节序表示
};
struct sockaddr_in6
{sa_family_t sin6_family; // 地址族: AF_INET6
u_inet16_t sin6_port; // 端口号:用网络字节序表示
u_int32_t sin6_flowinfo; // 流信息,应设置为 0
struct in6_addr sin6_addr; // IPV6 地址结构体
u_int32_t sin6_scope_id; // scope ID,尚处于试验阶段
};

IP地址转换函数

通常,人们习惯用点分十进制字符串表示 IPV4 地址,但编程中我们需要先把它们转化为整数方能使用,下面函数可用于点分十进制字符串表示的 IPV4 地址和网络字节序整数表示的 IPV4 地址之间的转换

#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp); //字符串表示的 IPV4 地址转化为网
络字节序
char* inet_ntoa(struct in_addr in); // IPV4 地址的网络字节序转化为字符
串表示

函数名中p表示点分十进制的字符串形式,to表示到,n表示network

2.socket编程主要API函数(网络编程接口)

2.1socket函数

​ socket()方法是用来创建一个套接字,有了套接字就可以通过网络进行数据的收发。这也是为什么进行网络通信的程序首先要创建一个套接字。创建套接字时要指定使用的服务类型,使用 TCP 协议选择流式服务(SOCK_STREAM) 。

int socket(int domain, int type,int protocal);

函数描述 创建socket

参数说明

(1)domain–协议版本

AF_INET OPV4 AF_INET6 IPV6 AF_UNIX AF_LOCAL(本地套接字使用)

(2)type–协议类型

SOCK_STREAM–流式,默认使用TCP协议

SOCK_DGRAM–报式,默认使用UDP协议

(3)protocol

一般填0,表示使用对应类型的默认协议

返回值

成功返回一个大于0的监听文件描述符,该文件描述符用于监听客户端连接,失败返回 -1,并设置errno

​ 当调用socket函数以后, 返回一个监听文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列(前提是在监听队列的时候才会存在)

​ 因此无法直接操作内核,而是对文件描述符进行操作,如read/write/close等。

2.2bind函数

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

​ 访问服务器需要绑定一个端口,不绑定端口也可以,但每次启动端口所获得的端口号式随机的,对方不知道发送数据的一方,无法对发送方进行访问。

​ bind()方法是用来指定套接字使用的 IP 地址和端口。 IP 地址就是自己主机的地址,如果主机没有接入网络,测试程序时可以使用回环地址“127.0.0.1”。端口是一个 16位的整形值,一般 0-1024 为知名端口,如 HTTP 使用的 80 号端口。这类端口一般用户不能随便使用。其次, 1024-4096 为保留端口, 用户一般也不使用。 4096 以上为临时端口,用户可以使用。在Linux 上, 1024 以内的端口号,只有 root 用户可以使用。

函数描述 将socket文件描述符和IP、PORT端口号绑定

参数说明

(1)sockfd

调用socket函数返回的文件描述符

(2)addr

本地服务器的IP地址和PORT,存放IP端口

struct sockaddr_in serv;
serv.sin_family = AF_INET;
ser.sin_port = htons(8888);
//serv.sin_addr.s_addr = htonl(INADDR_ANY);
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

(3)addrlen

addr变量占用的内存大小

2.3listen函数

​ listen()方法是用来创建监听队列。 监听队列有两种,一个是存放未完成三次握手的连接,一种是存放已完成三次握手的连接。 listen()第二个参数就是指定已完成三次握手队列的长度。

int listen(int sockfd,int backlog);

函数描述 服务端调用,将套接字由主动变为被动

参数说明

(1)sockfd

调用socket函数返回的文件描述符

(2)backlog

同时请求连接的最大个数(还未建立连接的客户端端口),最大为128,该参数意义不大,但不可以为0

返回值

成功返回0,失败返回-1并设置errno

2.4accept函数

​ accept()处理存放在 listen 创建的已完成三次握手的队列中的连接。每处理一个连接,则accept()返回该连接对应的套接字描述符。如果该队列为空,则 accept 阻塞。

int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);

函数描述 服务端调用,从已连接队列中接受一个连接,并获得一个新的通信文件描述符

accept函数是一个阻塞函数,如果没有新的连接请求,就会一直阻塞

参数说明

(1)sockfd

调用socket函数返回的文件描述符

(2)addr

传出参数,保存客户端的地址信息

(3)addrlen

传入传出参数,addr变量所占内存空间大小

返回值

成功返回一个新的通信文件描述符,用于和客户端通信,失败返回-1, 并设置errno值.

2.5connect函数

​ connect()方法一般由客户端程序执行,需要指定连接的服务器端的 IP 地址和端口。该方法执行后,会进行三次握手, 建立连接。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7LYv9iKy-1667036779168)(C:\Users\Iric Zhang\AppData\Roaming\Typora\typora-user-images\image-20221029173512027.png)]

int connect(int sockfd, const struct socaddr* addr, socklen_t addrlen);

函数描述

客户端主动调用,连接服务器

参数说明

(1)sockfd

调用socket函数返回的文件描述符

(2)addr

服务端的地址信息

(3)addrlen
addr变量的内存大小

返回值

成功返回0,失败返回-1并设置errno

2.6send、recv、close函数

以上完成之后使用recv/send进行读写,读写数据又是也可以使用read/write,但两者都是配套使用

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//对应recv和send这两个函数flags直接填0

send()方法用来向 TCP 连接的对端发送数据。 send()执行成功,只能说明将数据成功写入到发送端的发送缓冲区中,并不能说明数据已经发送到了对端。 send()的返回值为实际写入到发送缓冲区中的数据长度。

recv()方法用来接收 TCP 连接的对端发送来的数据。 recv()从本端的接收缓冲区中读取数据,如果接收缓冲区中没有数据,则 recv()方法会阻塞。返回值是实际读到的字节数,如果recv()返回值为 0, 说明对方已经关闭了 TCP 连接。

数据发送并接受完毕之后,使用close()方法用来关闭 TCP 连接。此时,会进行四次挥手。

2.7socketAPI编程流程图

代码测试过程中可以使用netstat进行查看监听状态和连接状态

3.代码实现

3.1服务端开发代码

服务端的开发流程

1.创建socket,返回一个文件描述符sockfd—socket(),该文件描述符用于监听客户端连接
2.将sockfd和IP端口进行绑定----bind();
3.将sockfd由主动变为被动监听----listen();
4.接受一个新的连接,得到一个文件描述符c----accept(),该文件描述符是用于和客户端进行通信;
5.while(1)
{
接收数据—recv()
发送数据—send()
}
6.关闭监听和通信文件描述符----close(sockfd) close©;

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{//创建socket----int socket(int domain, int type,int protocal);int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd != -1);//绑定bind----int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);struct sockaddr_in saddr, caddr,memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("192.168.31.143");int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));assert(res != -1);//监听listen----int listen(int sockfd,int backlog);listen(sockfd, 5);while(1){//接受连接accept----int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);int c = accept(sockfd,(struct sockaddr*)&caddr, &len);printf("accept client ip:%s, port = %d\n",inet_ntoa(caddr.sin_addr), ntohs(caddr.sin+port));if(c < 0){continue;}printf("accept c = %d\n",c);char buff[128];//读数据recv(c, buff, 127, 0);printf("buff = %s\n",c);//写数据send(c, "ok", 2, 0);//关闭监听close(c);}//关闭通信描述符close(sockfd);exit(0);
}

测试工具:nc

3.2客户端开发代码

客户端开发流程:

1.创建socket, 返回一个文件描述符sockfd—socket(),该文件描述符是用于和服务端通信;
2.连接服务端—connect() ;
3.while(1)
{
//发送数据—send

send写入发送缓冲区,内核负责从发送缓冲区拿出数据,网卡发送数据

​ //接收数据—recv
​ };

4.关闭通信描述符close(sockfd);

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{//创建socketint sockfd = socket(AF_INET, SOCKET_STREAM, 0);assert(sockfd != -1);//连接服务器connect----int connect(int sockfd, const struct socaddr* addr, socklen_t addrlen);struct sockaddr_in saddr;memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("192.168.31.143");int res = connect(sockfd, (struct saddr*)&saddr, sizeof(saddr));assert(res != -1);//发送数据printf("input:\n");char buff[128];fgets(buff, 127, stdin);send(sockfd, buff, strlen(buff), 0);//接收数据memset(buff, 0, 128);recv(sockfd, buff, 127, 0);printf("read:%s\n",buff);close(sockfd);exit(0);
}

【Linux】SOCKET编程相关推荐

  1. linux socket 编程

    socket  <script type="text/javascript"> </script> <script type="text/j ...

  2. Linux Socket编程

    IP socket 是在其上建立高级Internet 协议的最低级的层:从HTTP到SSL到POP3到Kerberos再到UDP-Time,每种Internet协议都建立在它的基础上.为了实现自定义的 ...

  3. Windows Socket和Linux Socket编程的区别

    2019独角兽企业重金招聘Python工程师标准>>> 1.一些常用函数的移植 http://www.vckbase.com/document/viewdoc/?id=1586 2. ...

  4. Linux Socket编程入门——浅显易懂

    文章目录 1. 概述 2. Socket 3. 网络字节序 4. sockaddr 数据结构 5. 网络套接字API函数  5.1 socket()  5.2 bind()  5.3 listen() ...

  5. linux下sig_pipe函数,linux socket编程 出现信号SIGPIPE,分析及解决

    在编写一个仿QQ软件,C/S模式.出现的问题:当客户机关闭时,服务器也随着关闭,纠结很久之后,我gdb了下,出现下面提示信息: Program received signal SIGPIPE, Bro ...

  6. 【Linux Socket 编程入门】05 - 拉个骡子溜溜:TCP编程模型代码分析

    (一) 看看以前学了啥 前面介绍了socket的分类,IP地址,端口号(port),常用的socket数据结构以及常用的函数.现在我们来看一个例子,看看socket编程究竟是什么. (二) 一图看懂客 ...

  7. linux socket编程epoll模型实现群发消息

    1.实现功能 本代码主要实现了socket编程epoll模型实现多个客户端连接服务器,客户端可以进行群发消息和接收用户输入文本信息,然后发送该信息给服务器,服务器收到后发送应答信息.客户端接收并显示该 ...

  8. Linux Socket编程(不限Linux)

    我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web 服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠s ...

  9. Linux Socket编程的一些总结

    最近写了一些Linux下网络编程的一些程序,做几点总结吧. 先给出客户端后服务器的一些Socket初始化的代码,以后可以直接拿来调用. 客户端Socket初始化代码 #include <stdi ...

  10. 【Linux网络】Linux Socket编程 TCP协议

    话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程 ...

最新文章

  1. Java 责任链模式
  2. 自学python可以做什么兼职-一行生财:毕业100天,通过副业赚(挣)到我的第一个10w...
  3. php -i | grep configure,PHP7中I/O模型内核剖析详解
  4. Push to origin/master was rejected解决方法
  5. 这两款无“节操”的浏览器,在315被曝光后,终于被下架了
  6. Spring Schedule定时关单快速入门
  7. linux内核分析——扒开系统调用的三层皮(上)
  8. layui表头样式_js相关:layui中table表头样式修改方法
  9. Flink的Union算子和Connect算子,流合并
  10. Python PIL库处理图片常用操作,图像识别数据增强的方法
  11. android 如何实现多级树形结构图(至少5级)
  12. win03组策略-入门篇
  13. python坐标轴拉伸_python-Matplotlib垂直拉伸histogram2d
  14. java 百度地图 经纬度_Java百度地图经纬度纠偏
  15. OSChina 周三乱弹 —— 九招助你工资秒长(干货哟)
  16. 最新2019年dnf辅助制作视频教程
  17. ThingJS摄像机总结
  18. Ubuntu常用软件安装,持续更新中。。。
  19. 上市公司创新研发支出数据(2006-2018年)
  20. 【读书笔记】码农翻身 - 简介

热门文章

  1. css3 制作音乐播放器音乐播放跳动音符
  2. 堡垒机是什么,堡垒机的作用在哪里
  3. 【python】控制鼠标定时移动 防止屏幕锁定 并生成可执行文件exe
  4. 如何修复png图片?简单的方法
  5. NAND FLAASH基础
  6. 2021年终总结暨2022年计划安排
  7. XRename(文件文件夹超级重命名工具)简介
  8. Java进阶(四)多态
  9. C++ 实现太阳系行星系统
  10. selenium+Java切换窗口句柄