在linux系统下用C/C++ Socket实现最简单的服务器,并解释使用到的函数。
1 创建Socket
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数说明:
domain
:表示要使用的网络协议族,常见的取值有:AF_INET
:IPv4协议族。AF_INET6
:IPv6协议族。AF_UNIX
:UNIX域协议族(用于本地进程间通信)。
type
:表示套接字类型,常见的取值有:SOCK_STREAM
:面向连接的流套接字,在TCP协议中使用。SOCK_DGRAM
:无连接的数据报套接字,在UDP协议中使用。
protocol
:指定协议类型,通常设置为0,表示根据domain
和type
自动选择合适的协议。
返回值:
- 成功:返回新创建的套接字描述符。
- 失败:返回-1,并设置全局变量
errno
表示错误代码。
2 命名Socket
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
参数说明:
sockfd
:表示要进行绑定的套接字的文件描述符。addr
:指向struct sockaddr
类型的指针,它表示要绑定的地址。通常会使用struct sockaddr_in
结构体来表示 IPv4 地址(也可以使用struct sockaddr_in6
来表示 IPv6 地址)。addrlen
:表示addr
的长度。
其中addr通常使用sockaddr_in结构如下:
#include <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family; // 地址族AF_INET
in_port_t sin_port; // 16 位端口号
struct in_addr sin_addr; // 32 位 IP 地址
};
返回值:
- 成功:返回0。
- 失败:返回-1,并设置全局变量
errno
表示错误代码 。
3 监听Socket
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数说明:
sockfd
:表示要监听的套接字的文件描述符。backlog
:表示监听队列的最大长度。
返回值:
- 成功:返回0。
- 失败:返回-1,并设置全局变量
errno
表示错误代码 。
4 接受连接
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
参数说明:
sockfd
:表示监听套接字的文件描述符。addr
:指向用于存储远程客户端地址的结构体指针,可以为NULL
。addrlen
:表示addr
结构体的长度。
返回值:
- 成功:返回新创建的套接字的文件描述符。
- 失败:返回-1,并设置全局变量
errno
表示错误代码 。
此处注意accept函数返回的是一个新的套接字,后续和该客户端通信都使用该套接字。
5 读取数据
#include <sys/socket.h>
int recv(int sockfd, void* buf, size_t len, int flags);
参数说明:
sockfd
:要接收数据的套接字描述符。buf
:用于存储接收数据的缓冲区的指针。len
:要接收的数据的最大长度。flags
:可选的标志参数,用于控制接收操作的行为,如:- 0:默认标志,无特殊行为。
MSG_PEEK
:从接收缓冲区中查看数据,但不将其从缓冲区中删除(下一次调用recv仍然可以接收到同样的数据)。MSG_WAITALL:读操作仅在读取到指定数量的字节后返回。
MSG_DONTWAIT:非阻塞模式。
返回值:
- 成功:返回实际读取的字节数。
- 对方关闭连接:返回0。
- 失败:返回-1,并设置全局变量
errno
表示错误代码 。
完整代码
给出完整的简易服务器代码。
//server.cpp
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
int main() {
// 创建Socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
// 准备服务器地址
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(5712); // 端口号
// 绑定地址和端口
bind(serverSocket, (struct sockaddr *) &serverAddress, sizeof(serverAddress));
// 监听连接
listen(serverSocket, 5);
std::cout << "Server started." << std::endl;
// 接受客户端连接
struct sockaddr_in clientAddress;
socklen_t clientAddressSize = sizeof(clientAddress);
int clientSocket = accept(serverSocket, (struct sockaddr *) &clientAddress, &clientAddressSize);
std::cout << "New client connection accepted." << std::endl;
char buffer[1024] = {0};
// 读取客户端数据
ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
// 打印客户端发送的数据
std::cout << "Received from client: " << buffer << std::endl;
// 关闭客户端连接
close(clientSocket);
// 关闭服务器Socket
close(serverSocket);
return 0;
}
测试
编译运行服务器程序。
g++ server.cpp -o server
./server
另开一个终端,用nc指令作为客户端简单测试一下服务器。
nc 127.0.0.1 5712
Hello!
服务端显示相应信息,测试成功!