4. TCP编程
4.1 TCP介绍
- 面向连接的流式协议;可靠、出错重传、且每收到一个数据都要给出相应的确认
- 通信之前需要建立链接
- 服务器被动链接,客户端是主动链接
TCP编程流程
服务器:
1. 创建套接字 socket()
2. 将套接字与服务器网络信息结构体绑定 bind()
3. 将套接字设置为监听状态 listen()
4. 阻塞等待客户端的连接请求 accept()进行通信 recv()/send()
5. 关闭套接字 close()
客户端:
1. 创建套接字 socket()
2. 发送客户端连接请求 connect()
3. 进行通信 send()/recv()
4. 关闭套接字 close()
4.2 TCP编程-socket
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type, int protocol);
功能:创建一个套接字,返回一个文件描述符;
参数:
domain:通信域,协议族
AF_UNIX 本地通信
AF_INET ipv4网络协议
AF_INET6 ipv6网络协议
AF_PACKET 底层接口
type:套接字的类型
SOCK_STREAM 流式套接字(tcp)
SOCK_DGRAM 数据报套接字(udp)
SOCK_RAW 原始套接字(用于链路层)
protocol: 附加协议,如果不需要,则设置为0
返回值:
成功:文件描述符
失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int socket_id;
if ((socket_id = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("FAIL TO CREAET TCP SOCKET");
exit(1);
}
printf("socket_id = %d \n", socket_id);
return 0;
}
输出结果
socket_id = 3
4.3 TCP客户端 -connect、send、recv
4.3.1 connect函数
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:给服务器发送客户端的连接请求;
参数:
sockfd:文件描述符,socket函数的返回值
addr:要连接的服务器的网络信息结构体(需要自己设置);
addrlen:add的长度返回值:
成功:0
失败:-1
4.3.2 send函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:文件描述符
客户端:socket函数的返回值;
服务器:accept函数的返回值;
buf:发送的数据
len:buf的长度
flags:标志位
0 阻塞
MSG DONTWAIT 非阻塞
返回值:
成功:发送的字节数
失败:-1
4.3.3 recv函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收数据
参数:
sockfd:文件描述符
客户端:socket函数的返回值
服务器:accept函数的返回值
buf:保存接收到的数据
len:buf的长度
flags:标志位
0 阻塞。
MSG DONTWAIT 非阻塞
返回值:
成功:接收的字节数
失败:-1
注意:如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0
4.4 TCP服务端- bind、listen、accept
4.4.1 bind函数
#include<sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定;
参数:
sockfd:文件描述符,socket的返回值;
addr:网络信息结构体,通用结构体(一般不用)struct sockaddr
网络信息结构体 sockaddr_in
#include <netinet/in.h>
struct sockaddr_in
addrlen:addr的长度
返回值:
成功:0
失败:-1
4.4.2 listen函数
#include <sys/socket.h>
int listen(intsockfd, int backlog)
功能: 将套接字由主动修改为被动
使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接;
参数:
sockfd: socket监听套接字
backlog: 连接队列的长度,即同一时间允许客户端连接的个数;
返回值:
成功:返回 0
失败:其他
4.4.3 accept函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
阻塞等待客户端的连接请求
参数:
sockfd:文件描述符,socket函数的返回值;
addr:接收到的客户端的信息结构体(自动填充,定义变量即可);
addrlen:addr的长度
返回值:
成功:新的文件描述符(只要有客户端连接,就会产生新的文件描述符; 这个新的文件描述符专门与指定的客户端进行通信的)
失败:-1
4.5 close 函数、三次握手、四次挥手
4.5.1 close函数
- 使用 close 函数即可关闭套接字
关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包 - 做服务器时
- 关闭监听套接字将导致服务器无法接收新的连接,但不会影响已经建立的连接
- 关闭 accept返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器的监听
- 做客户端时
关闭连接就是关闭连接,不意味着其他
4.5.2 三次握手
4.5.3 四次挥手
4.6 TCP并发服务器
TCP原本不是并发服务器,TCP服务器同一时间只能与一个客户端进行通信。
TCP不能实现并发的原因:TCP服务端有两个阻塞函数accept和recv
,所以导致运行一个函数的时候,另一个函数无法执行;这样使得TCP无法保证一边连接客户端,一边与其他客户端通信。
那么,如何实现TCP并发服务器呢?
- 使用多进程实现TCP并发服务器
- 使用多线程实现TCP并发服务器
4.6.1 多进程实现并发
02_mulit_process_tcp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#define MAX_BUFFER_SIZE 1024
#define PORT 8080
#define MAX_CONNECTIONS 5
void handle_client(int client_socket) {
char buffer[MAX_BUFFER_SIZE];
ssize_t num_bytes;
while ((num_bytes = recv(client_socket, buffer, MAX_BUFFER_SIZE, 0)) > 0) {
buffer[num_bytes] = '\0';
printf("Received message: %s\n", buffer);
// 处理客户端请求,这里简单地将收到的消息原样发送回去
send(client_socket, buffer, num_bytes, 0);
}
if (num_bytes == 0) {
printf("Client disconnected\n");
} else {
perror("recv");
}
close(client_socket);
}
int main() {
int server_socket, client_socket;
struct sockaddr_in server_address, client_address;
socklen_t client_address_size;
pid_t child_pid;
int status;
// 创建套接字
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置服务器地址和端口
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(PORT);
// 绑定套接字到指定地址和端口
if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听连接请求
if (listen(server_socket, MAX_CONNECTIONS) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d\n", PORT);
while (1) {
// 接受客户端连接请求
client_address_size = sizeof(client_address);
client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_address_size);
if (client_socket == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
// 创建子进程来处理客户端请求
child_pid = fork();
if (child_pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
// 子进程
close(server_socket);
handle_client(client_socket);
exit(EXIT_SUCCESS);
} else {
// 父进程
close(client_socket);
// 回收子进程资源,避免僵尸进程
waitpid(-1, &status, WNOHANG);
}
}
// 关闭服务器套接字
close(server_socket);
return 0;
}
03_multi_process_tcp_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define MAX_BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"
#define PORT 8080
int main() {
int client_socket;
struct sockaddr_in server_address;
char buffer[MAX_BUFFER_SIZE];
ssize_t num_bytes;
// 创建套接字
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置服务器地址和端口
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr(SERVER_IP);
server_address.sin_port = htons(PORT);
// 连接到服务器
if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}
printf("Connected to server\n");
while (1) {
printf("Enter a message (or 'q' to quit): ");
fgets(buffer, MAX_BUFFER_SIZE, stdin);
// 移除换行符
buffer[strcspn(buffer, "\n")] = '\0';
// 检查是否退出客户端
if (strcmp(buffer, "q") == 0) {
break;
}
// 发送消息给服务器
if (send(client_socket, buffer, strlen(buffer), 0) == -1) {
perror("send");
exit(EXIT_FAILURE);
}
// 接收服务器的响应
num_bytes = recv(client_socket, buffer, MAX_BUFFER_SIZE - 1, 0);
if (num_bytes == -1) {
perror("recv");
exit(EXIT_FAILURE);
} else if (num_bytes == 0) {
printf("Server disconnected\n");
break;
}
buffer[num_bytes] = '\0';
printf("Server response: %s\n", buffer);
}
// 关闭套接字
close(client_socket);
return 0;
}
4.6.2 多线程实现并发
void * thread_fun(){
}
scoketfd = socket();
bind();
listen();
while(1){
accept();
//只要有客户端,就创建子线程与之通信
pthread_create(,,thread_fun,);
pthread_detach();
}
4.7 web服务器介绍
web服务器又称为www服务器、网站服务器等
4.7.1 特点
- 使用HTTP协议与客户机浏览器进行交流
- 不仅能够存储信息,还支持用户通过web服务器运行脚本和程序
- 服务器一般部署在UNIX、Linux或者window等操作系统上,
4.7.2 HTTP协议
概念:一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
特点:
- 支持C/S架构
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径,常用方法:GET、POST
- 无连接: 限制每次连接只处理一个请求
- 无状态: 即如果后续处理需要前面的信息,它必须重传,这样可能导致每次连接传送的数据量会增大