文章目录
一、socket网络编程
0.参考
- 浅谈 Linux 网络编程 - Server 端模型、sockaddr、sockaddr_in 结构体
- server 端的套路
- 相关函数
- Socket编程详解:从基本概念到实例应用(TCP|UDP C语言实例详解)
- 编程基本步骤
- 函数原型及用法
- 编程实例
- 【网络篇】socket编程——TCP(史上最全)
- tcp详解
- socket编程实例
- 【Socket网络编程】12. send()、recv()、sendto() 和 recvfrom() 函数解析
- 函数详解
- 一文带你了解socket网络编程以及详解过程和原理
- 流程图
- 代码示例
- 函数API
1.socket编程的基本步骤
TCP开发流程:
-
服务器端:
- 创建套接字:int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
- 绑定地址:IP + Port
- struct sockaddr_in serverAddress;
- serverAddress.sin_family = AF_INET;
- serverAddress.sin_port = htons(port);
- serverAddress.sin_addr.s_addr = INADDR_ANY;
- bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
- 监听连接(设置连接上限):listen(serverSocket, backlog);
- 接受连接(实际上的监听):
- struct sockaddr_in clientAddress;
- int clientSocket = accept(serverSocket, (struct sockaddr*) &clientAddress, sizeof(clientAddress) );
- 发送和接收数据:
- send(clientSocket, buffer, size, 0);
- recv(clientSocket, buffer, size, 0);
- 关闭连接:close(clientSocket); close(serverSocket);
-
客户端:
- 创建套接字:int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
- 连接服务器:
- struct sockaddr_in serverAddress;
- serverAddress.sin_family = AF_INET;
- serverAddress.sin_port = htons(port);
- serverAddress.sin_addr.s_addr = inet_addr(serverIP);
- connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
- 发送和接收数据:
- send(clientSocket, buffer, size, 0);
- recv(clientSocket, buffer, size, 0);
- 关闭连接:close(clientSocket);
大致流程图:
2.函数原型及参数用法
1 socket()
socket()函数介绍
建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。
如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
2.bind()
给 socket 绑定一个 地址结构 (IP+port)
#include <arpa/inet.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:
- 成功:0
- 失败:-1 errno
3.listen()
设置 server 连接上限,设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)
int listen(int sockfd, int backlog);
参数说明:
- sockfd: socket() 函数的返回值
- backlog:上限数值。最大值 128.
返回值:
- 成功:0
- 失败:-1 errno
4.accept()
阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的 socket 文件描述符。
- accept 返回的 socket 才是真正与 client 建立连接的 socket。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
socket 函数创建的是 监听套接字(fd1),用于监听来自客户端的连接请求,和进行端口、IP 绑定。
由 accept 函数创建的是用于通信的套接字(fd2),用于和客户端建立连接,称为 通信套接字 。
5.send()
send()函数的用法;MSG_NOSIGNAL什么含义?有什么作用?以及flags中参数类型有哪些各自又起到什么作用?
用于在 TCP/IP 网络上发送数据的系统调用函数。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd 表示需要发送数据的 socket 文件描述符,buf 表示指向要发送数据的缓冲区的指针,len 表示要发送数据的长度,flags 是一个选项参数,它可以影响发送的行为。
返回发送的字节数。如果出现错误,它会返回 -1。
6.recv()
int recv( SOCKET s, char *buf, int len, int flags);
- 第一个参数指定接收端套接字描述符;
- 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
- 第三个参数指明buf的长度;
- 第四个参数一般置0。
如果s的接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕;
当协议把数据接收完毕,recv函数就把s的接收缓冲区中的数据copy到buf中。
7.无连接的发送与接收
Linux网络编程- recvfrom() & sendto()
-
sendto()
用于发送数据到一个指定的地址。它经常与无连接的数据报协议,如UDP一起使用 -
recvfrom()
用于从套接字接收数据。该函数通常与无连接的数据报服务(如 UDP)一起使用
8.connect()
客户端通过connect()来发起连接
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
- sockfd参数由socket系统调用返回一个socket
- serv_addr参数是服务器监听的socket地址
- addrlen参数则指定这个地址的长度。
9.close()和shutdown()
通过close()来关闭一个socket连接。实际是将该fd的引用计数减一。
#include<unistd.h>
int close(int fd); // fd是待关闭的socket
若要立即终止连接,可以使用shutdown系统调用。
#include<sys/socket.h>
int shutdown(int sockfd,int howto);
howto参数决定了shutdown的行为,可选关闭读、关闭写、关闭全部。
3.sockaddr_in结构体
sockaddr_in详解
用于表示Internet地址和端口号。通常与套接字(socket)API一起使用。
#include<netinet/in.h>
struct sockaddr_in{
sa_family_t sin_family; //地址簇(Address Family)
uint16_t sin_port; //16位TCP/UDP端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用
}
struct in_addr{
In_addr_t s_addr; // 32为IPv4地址
}
参数说明:
- sin_family:地址族,通常设置为AF_INET表示IPv4协议。
- sin_port:端口号,以网络字节序表示。
- sin_addr:IP地址,以网络字节序表示。
- sin_zero:填充字段,通常设置为0。
在早期的时候,使用的是 sockaddr,后来出现了新的、用于 ipv4 的 sockaddr_in,所以现在使用的都是 sockaddr_in。
sockaddr_in 相比于 sockaddr,二者的大小都是一样的,只是在空间划分上分的更细
使用sockaddr_in结构体时,需要将其类型转换为sockaddr类型,因为套接字API中的大多数函数都需要传入sockaddr类型的指针作为参数。
- 可以使用强制类型转换将sockaddr_in类型转换为sockaddr类型
struct sockaddr_inaddr;// 设置addr的字段值
...
// 将addr转换为sockaddr类型
struct sockaddr* sa = (struct sockaddr*)&addr;
4.socket其他函数
4.1 setsockopt()
setsockopt()函数
setsockopt()函数功能介绍
获取或者设置与某个套接字关联的选项。
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数说明:
- int sockfd: 很简单,socket句柄
- int level: 选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次
- int optname: 需设置的选项
- const void *optval: 指针,指向存放选项值的缓冲区;
- 对于getsockopt(),指向返回选项值的缓冲。
- 对于setsockopt(),指向包含新选项值的缓冲。
- socklen_t optlen: optval缓冲区的长度。
- 对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。
- 对于setsockopt(),现选项的长度。
返回值:成功执行时,返回0。失败返回-1,errno被设为以下的某个值
- EBADF:sock不是有效的文件描述词
- EFAULT:optval指向的内存并非有效的进程空间
- EINVAL:在调用setsockopt()时,optlen无效
- ENOPROTOOPT:指定的协议层不能识别选项
- ENOTSOCK:sock描述的不是套接字
4.2 socketpair()
socketpair的用法和理解
用于创建一对无名的、相互连接的套接字。
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int d, int type, int protocol, int sv[2]);
如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1,错误码保存于errno中。
基本用法:
- 这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读;
- 如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功;
- 读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述副sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。