Linux系统编程------TCP服务器
服务器流程
- 创建流式套接字,通过socket实现
- 填充服务器的网络信息结构体,struct sockaddr_in
- 将套接字与服务器的网络信息结构体绑定,通过bind实现
- 将套接字设置成被动监听状态,通过listen实现
- 阻塞等待客户端连接,通过accept实现
- 收发数据,通过recv/send实现
- 关闭套接字,通过close实现
socket函数
c
int socket(int domain, int type, int protocol);
所需头文件:sys/socket.h
功能:创建socket
domain:协议族
type:socket类型
protocol:附件协议,无协议写0
返回值:成功返回套接字,失败返回-1并设置errno
domain取值
| 宏 | 说明 |
|---|---|
| AF_INET | IPV4 |
| AF_INET6 | IPV6 |
| AF_PACKET | 原始套接字使用 |
| AF_UNIX | 本地通信 |
| AF_LOCAL | 本地通信 |
socket取值
| 宏 | 说明 |
|---|---|
| SOCK_STREAM | TCP使用 |
| SOCK_DGRAM | UDP使用 |
| SOCK_RAW | 原始套接字使用 |
bind函数
c
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
所需头文件:sys/socket.h
功能:将套接字与服务器的网络信息结构体绑定
sockfd:文件描述符,由socket函数返回
addr:地址结构体指针,指向要绑定给sockfd的协议地址,结构体根据创建sock时的协议族不同而不同,这里以IPV4为例。
addrlen:地址结构体长度
返回值:成功返回0,失败返回-1并设置errno
sockaddr结构体
c
struct sockaddr_in {
sa_family_t sin_family; /*地址族*/
in_port_t sin_port; //16位端口号
struct in_addr sin_addr //32位IP地址
};
struct in_addr{
uint32_t s_addr; //32位IPV4地址
};
listen函数
c
int listen(int sockfd, int backlog);
所需头文件:sys/socket.h
功能:将套接字设置成被动监听状态
sockfd:要监听的套接字
backlog:连接队列的长度
返回值:成功返回0,失败返回-1并设置errno
accept函数
c
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
功能:阻塞等待客户端连接
sockfd:处于监听状态的socket
addr:用于保存客户端地址的结构体指针,如果不关系客户端信息,可以传NULL
addrlen:输入时为addr的缓冲区大小,输出时为实际地址长度,如果不关系客户端信息,可以传NULL。
返回值:成功返回新的socket文件描述符(由内核生成,代表着与返回客户端TCP连接,专用于与客户端通信),失败返回-1并设置errno
recv函数
c
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
所需头文件:sys/socket.h
功能:接收数据
sockfd:客户端的socket套接字
buf:存储接受的数据
len:数据大小
flags:控制选项(如MSG_DONTWAIT非阻塞,MSG_PEEK窥视数据, 0阻塞)
返回值:成功返回实际接受数据字节数,失败返回-1并设置errno
send函数
c
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
所需头文件:sys/socket.h
功能:发送数据
sockfd:客户端的sock套接字
buf:要发送数据的首地址
len:数据大小
flags:控制选项(如MSG_DONTWAIT非阻塞,MSG_PEEK窥视数据, 0阻塞)
返回值:成功返回实际发送数据字节数,失败返回-1并重置错误码
close函数
c
int close(int fd);
所需头文件:unistd.h
fd:要关闭的套接字
返回值:成功返回0,失败返回-1
实例:实现一个TCP服务器
代码:
c
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
int main(int argc, char** argv) {
//创建流式套接字
struct sockaddr_in server_info;
struct sockaddr_in client_info;
socklen_t client_info_len = sizeof(client_info);
int ret;
int listen_fs = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fs < 0) {
perror("socket创建失败");
return -1;
}
//配置服务器地址结构体
//清空结构体
memset(&server_info, 0, sizeof(server_info));
memset(&client_info, 0, sizeof(client_info));
//初始化IPV4协议族
server_info.sin_family = AF_INET;
//初始化端口号
server_info.sin_port = htons(8888);
//初始化IP
server_info.sin_addr.s_addr = inet_addr("192.168.203.141");
//绑定套接字与地址
ret = bind(listen_fs,(struct sockaddr*)&server_info, sizeof(server_info));
if (ret < 0) {
perror("绑定失败");
return -1;
}
//监听
ret = listen(listen_fs, 128);
if (ret < 0) {
perror("监听失败");
return -1;
}
//循环等待客户端连接
while(true) {
int connect_fd = accept(listen_fs,(struct sockaddr*)&client_info, client_info_len );
if(connect_fd == -1) {
perror("等待连接失败");
return -1;
}
printf("客户端[%s:%d] 连接到服务器", inet_ntoa(client_info.sin_addr), ntohs(client_info.sin_port));
//处理客户端通信(内部循环);
while(true) {
char buf[128] = {0};
//int nbtyes = read(connect_fd, buf, sizeof(buf));
int nbytes = recv(connect_fd, buf, sizeof(buf), 0);
if (nbytes == -1) {
perror("读取失败");
return -1;
}
if (nbytes == 0) {
printf("客户端断开连接\n");
break;
}
printf("客户端[%s:%d]发来的数据:%s\n",inet_ntoa(client_info.sin_addr),ntohs(client_info.sin_port), buf);
//回显数据
int nbytes_send = send(connect_fd, buf, sizeof(buf), 0);
if (nbytes_send == -1) {
perror("发送失败");
return -1;
}
}
}
return 0;
}
运行结果: