--------------| socket 使用与相关函数 |--------------
1. 服务端
a. 头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8888 // 定义端口
#define MAXLEN 1024
b. 服务器设置流程
- 创建套接字,用以绑定本机ip并监听访问ip
// 创建一个用以监听的套接字,并设置类型,此时还没绑定ip和端口
int listenfd = socket(AF_INET,SOCK_STREAM,0); // 成功返回非负数
- 绑定套接字
// socket绑定需要特定的套接字地址信息结构体,
// 有两种套接字地址信息结构体 sockaddr 和 sockaddr_in
// sockaddr 是sockaddr_in 的抽象,内部变量只有 af_family 和 port 两个
// sockaddr_in 是具体的地址信息结构体,针对 ipv4 专用的结构体
struct sockaddr_in serverAddr;
memset(&serverAddr,0,siezof(serverAddr)); // 结构体初始化,清 0
serverAddr.sin_family = AF_INET; // 指定地址家族 ipv4
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机所有地址,转网络字节序
serverAddr.sin_port = htons(PORT); // 端口转网络字节序
// 将结构与监听套接字进行绑定,
// 处于函数通用性,bind()、listen()、accept()等函数
// 都用 struct sockaddr 的抽象结构体 成功返回 0 ,失败返回负数;
bind(listenfd,(struct sockaddr*)serverAddr,sizeof(serverAddr));
- 设置监听
// 使用listen()函数设置监听属性,将socket主动编程被动监听
// 成功返回 0 ,失败返回负数
listen(listenfd,5); // 第二个参数表示指定队列中最多可有的连接请求数
- 等待并接受其他主机的连接
// 接受客户端的连接,并返回客户端的socket句柄(文件描述符)
struct sockaddr_in clientAddr;
int clientfd = accept(listenfd,(struct sockaddr*)clientAddr,sizeof(clientAddr)); // 之后就是通过 clientfd 与客户端进行接收和发送消息
- 接受和发送数据信息
// 接受信息
char str[256];
int n = read(clientfd,str,sizeof(str)); // 读取网络数据到str,返回值为读取到的数量
// 发送信息
write(clitentfd,str,n); // 发送数据给 clientfd
- 结束服务并释放所有 socket句柄(文件描述符)
close(clientfd); // 关闭客户端句柄
close(listenfd); // 关闭服务器句柄
2. 客户端
a. 头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8888 // 定义端口
#define MAXLEN 1024
b. 客户端设置流程
创建套接字 —> 连接服务器 —> 发送和接受数据
- 创建套接字
int socketfd;
socketfd = socket(AF_INET,SOCK_STREAM,0); // 获取套接字句柄 成功返回非 0
- 连接服务器
// 设置服务器信息
struct sockaddr_in serverAddr;
memset(&serverAddr,0,sizeof(serverAddr));
serverAddr.sin_family = AF_INET; // 设置地址家族 ipv4
serverAddr.sin_port = htons(PORT); // 设置端口
inet_pton(AF_INET,"192.168.1.8",&serverAddr.sin_addr); // 指定ip转为网络字节序
// 连接服务器
connect(socketfd,(struct sockaddr*)serverAddr,sizeof(serverAddr)); // 连接服
// 通过socketfd实现数据的 接受 和 发送
- 数据的 发送 与 接收
// 发送数据
char str[256] = "发送的数据";
int sendN = write(socket,str,sizeof(str)); // 返回值为成功发送的个数
int recvN = read(socket,str,sizeof(str)); // 返回值为接收的数据个数
write(STDOUT_FILENO, str, recvN); // 像标准输出设备输出接受的数量
- 释放相关句柄
close(socket); // 释放文件描述符 linux下一切皆文件
-------------------|其他相关函数|-------------------
1. TCP连接相关函数
TCP | IO读写 |
---|---|
send() | write() |
recv() | read() |
send(); // 这两个函数可以作为write() 和 read() 的替代品
recv(); // 提供了更多的错误检查和控制选项,发送接受大小可以指定,
// 还有一些发送接受的标志
2. UDP连接相关函数
发送 | 接收 | |
---|---|---|
UDP连接 | sendto() | recvfrom() |
sendto(); // 发送数据是需要目标 ip 和 port 作为参数
recvfrom(); // 接受数据会返回发送方的 ip 和 port
3. 其他函数
其他socket函数 | poll() | select() |
poll()和select()函数是用于多路复用I/O操作的重要工具,它们允许一个进程同时监控多个文件描述符(如套接字、管道等)的状态变化,而无需实际读取或写入数据。这两种函数特别适合于需要处理大量并发连接的服务器应用程序,如Web服务器、代理服务器或聊天服务器等。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
fds:一个struct pollfd数组的指针,用于指定监控的文件描述符及其状态变化。
nfds:数组fds中元素的数量。
timeout:指定阻塞等待的时间限制(毫秒为单位)。若为负数,则poll()将立即返回;若为0,则poll()不会阻塞,立即返回当前状态;若为正数,则poll()将在指定的时间内等待,超时后返回。
返回值:
若有文件描述符变为就绪状态,则返回就绪的文件描述符个数。
若超时,则返回0。
若发生错误,则返回-1,并设置errno。
*/
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
/*
nfds:监控的最大文件描述符+1。
readfds:指向一个fd_set类型的指针,用于指定希望监控读就绪的文件描述符集合。
writefds:指向一个fd_set类型的指针,用于指定希望监控写就绪的文件描述符集合。
exceptfds:指向一个fd_set类型的指针,用于指定希望监控异常条件的文件描述符集合。
timeout:指向一个struct timeval类型的指针,用于指定阻塞等待的时间限制。如果timeout为NULL,则select()将无限期阻塞,直到有文件描述符变得可读、可写或发生异常;如果timeout为非零值,select()将在指定的时间内等待,超时后返回。
返回值:
若有文件描述符变为就绪状态,则返回就绪的文件描述符个数。
若超时,则返回0。
若发生错误,则返回-1,并设置errno。
*/