cpp

算法

实现一个基于TCP的最小服务器程序

  • 在本机8080端口启动一个tcp服务器
  • 等待客户端连接
  • 接受一个客户端后向其发送一条字符串消息,然后关闭连接并退出程序

为了实现这套流程,我们需要以下步骤

  1. 创建socket,申请网络通信资源(这里的是监听socket_fd)
  2. 准备服务器地质结构(IP+端口)
  3. bind绑定socket和地址(占用端口)
  4. 进入监听状态等待客户端,为这个客户端创建一个专属socket
  5. 向客户端发送数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
using namespace std;

int main(){
//server_fd不是socket对象,是socket的文件描述符
int server_fd=socket(AF_INET,SOCK_STREAM,0);//AF_INET表示IPV4,SOCK_STREAM表示TCP协议
if(server_fd==-1){
cerr<<"Socket create failed"<<endl;
return -1;
}
//以下三行告诉你。我要创建一个IPV4服务器,监听本机所有ip,端口号是8080
sockaddr_in address;//sockaddr_in是IPV4专用地址结构体
address.sin_family=AF_INET;//告诉系统这是一个IPV4地址
address.sin_addr.s_addr=INADDR_ANY;//sin_addr是一个结构体,s_addr里面存真正的ip字段、
//INADDR_ANY表示监听本机所有网卡的ip地址,机器可能有127.0,0.1本地回环,192.168.1.100局域网,公网ip。
//这个表示不管从哪个ip连接过来,只要端口对就接
//也可以指定ip写,比如inet_addr("127.0.0.1")
address.sin_port=htons(8080);
//计算机存整数时有两种方式,小端序如x86,大端序如网络传输,网络规定统一使用大端序
//htons把主机字节序转到网络字节序,htons=Host TO Network Short
//端口 htons ,ip htonl

if(bind(server_fd,(struct sockaddr*)&address,sizeof(address))<0){
return -1;
}
//bind作用是把一个socket和一个ip地址和端口号绑定在一起,如果不Bind,服务器是没有地址的
//这里的socket_fd是之前socket得到的文件描述符,告诉系统是哪个手机要绑定
//需要把IPV4地址当成通用地址用,是c时代遗留下来的
//小于0表示失败,可能端口已经被占用或者权限不够或者地址非法
if(listen(server_fd,3)<0){
return -1;
}
//把普通socket变成监听socket,没有listen(),服务器不能连接
//3表示监听队列的长度,最多允许多少个客户端
cout<<"Listening on port 8080...(Press Ctrl+C to stop)"<<endl;

//等待一个客户端连接,并为这个客户创建一个专属通信socket
int new_socket;//这是一个通信socket,专门用来服务一个客户端,可以用来send,recv,close
int addrlen=sizeof(address);
new_socket=accept(server_fd,(struct sockaddr*)&address,(socklen_t*)&addrlen);
//第一个参数表示从哪个监听socket上接人,第二个参数是一个输出参数,会把客户端的ip和端口写进去,第三个参数是告诉系统address有多大,输出实际写了多少字节
if(new_socket<0){
cerr<<"Accept failed"<<endl;
return -1;
}
cout<<"Connection accepted"<<endl;

const char* hello="Hello from server";//我准备要发送的数据
send(new_socket,hello,strlen(hello),0);
//第一个参数表示和哪个客户端通信,第二个参数是发送的数据地址,第三个是发送多少字节,第四个是默认行为
//send并不保证一次把所有数据都发完,返回值小于len是可能的
cout<<"Response sent."<<endl;

close(new_socket);//关闭和这个客户端的tcp连接。此时客户端再recv()会受到0.tcp会进行四次挥手,但是服务器仍然存在
close(server_fd);//服务器彻底下线
}

该代码展示了 TCP 服务器从创建、监听、接受连接到通信和关闭的完整流程,是理解 C++ 网络编程和操作系统网络接口的最小可运行示例。

多线程并发服务器

在刚刚的代码中我们可以发现一个显著问题,