TCP通信
- server
- 1. 创建套接字
- 2. 填充套接字
- 3. 将套接字和监听文件描述符绑定
- 4. 将_listensock设置为监听状态
- 5. 启动服务器
- accept()函数
- read()函数
- Server启动
- client
- 1. 创建套接字
- 2. 填充套接字
- connect()函数
- 3. 通过文件描述符向服务端发送信息
- client启动
server
server的启动方式是:./udpserver 8080 --> 格式就是./proc port端口
port端口自己指定
1. 创建套接字
_listensock = socket(AF_INET, SOCK_STREAM, 0);
AF_INET:表明域是ipv4
SOCK_STREAM:表示使用TCP连接,面向字节流的
__listensock:socket()函数返回的文件描述符是监听套接字,用于监听客户端的连接请求,并不是用来接收信息的
2. 填充套接字
//2.填充套接字
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = _port;
local.sin_addr.s_addr = INADDR_ANY; //INDAAR_ANY:为0.0.0.0,表示任何ip地址
这里的套接字存储的是服务端的地址信息,包含了服务器监听的ip地址信息和端口信息。
3. 将套接字和监听文件描述符绑定
bind(_listensock, (const struct sockaddr*)&local, sizeof(local)
bind
函数将服务器的监听套接字 _listensock
与 local
地址绑定,以便在该地址和端口上监听连接。
4. 将_listensock设置为监听状态
listen(_listensock, backlog)
listen()
函数的作用是将一个套接字设置为监听状态,使服务器能够接收客户端的连接请求。它主要用于TCP服务器的套接字初始化过程中。
在这里,就是将_listensock设置为监听状态。
backlog:连接队列的最大长度,也就是未处理的客户端连接请求数的上限。当有多个客户端同时请求连接时,系统会将这些连接请求放入一个队列中,backlog
就是该队列的大小。
backlog不宜设置过大,在本文中的设置如下:
const int backlog = 10;
5. 启动服务器
void Start()
{
while(true)
{
//1.获得client的套接字
struct sockaddr_in client;
//2.通过accept获取client的值
//accept用来接收客户端的连接请求,recvform用来接收客户端的数据
//listensock是用来接收客户端连接请求的,accept的返回值sock是操作系统用来和客户端一对一通信的
socklen_t len = sizeof(client);
int sock = accept(_listensock, (struct sockaddr*)&client, &len);
if (sock < 0)
{
cout << "accept 失败" << endl;
exit(ACCEOPT_ERRPR);
}
//启动服务器
ServerIO(sock);
}
}
accept()函数
int sock = accept(_listensock, (struct sockaddr*)&client, &len)
accept()
函数在服务器端的作用是接收客户端的连接请求,并为与该客户端的通信创建一个新的套接字sock。 这个新的套接字sock用于服务器和客户端之间的一对一数据传输。当有客户端发起连接请求时,服务器监听套接字_listensock会检测到,并通过 accept()
函数获取该请求并创建一个用于通信的套接字sock。
1. _listensock和sock的区别是什么?
_listensock
:监听套接字
- 用途:用于监听客户端的连接请求。
- 创建方式:通过
socket()
函数创建,之后使用bind()
将其绑定到特定的 IP 地址和端口,再通过listen()
函数使其进入监听状态。 - 特点:
_listensock
只负责监听,它不会直接用于数据传输。每当有客户端请求连接时,服务器会通过accept()
函数从连接队列中取出请求并创建新的通信套接字。 - 生命周期:通常服务器运行期间一直存在,直到服务器关闭才释放。
sock
:通信套接字
- 用途:用于与特定客户端之间进行数据传输。
- 创建方式:当客户端发起连接请求并被
accept()
函数接受时,系统会创建一个新的套接字(即sock
)来完成该客户端与服务器之间的通信。 - 特点:每个
sock
都是与一个客户端的唯一连接,负责传输和该客户端的所有数据。服务器可以有多个sock
,分别与不同客户端通信。 - 生命周期:当客户端断开连接或服务器结束与该客户端的会话时,
sock
会被关闭和释放。
2. accept()函数的详细解析?
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-
sockfd
:- 这是服务器的监听套接字文件描述符,通常由
socket()
函数和bind()
、listen()
函数配合创建并设置好。 - 它指定了服务器在该套接字上监听客户端连接请求。
- 这是服务器的监听套接字文件描述符,通常由
-
addr
:- 这是一个指向
sockaddr
结构体的指针,用于存储客户端的地址信息(如IP地址和端口号)。 - 该地址结构体在调用
accept()
后会被填充,包含客户端的 IP 地址和端口号,用于识别该连接的客户端。 - 在代码中,
(struct sockaddr*)&client
将客户端地址信息存储在client
结构体中。
- 这是一个指向
-
addrlen
:- 这是一个指向
socklen_t
类型的指针,表示addr
结构体的大小。 - 在调用
accept()
时,addrlen
应包含addr
的大小;accept()
返回时,它会被更新为客户端地址的实际长度。
- 这是一个指向
-
accept()
函数的返回值- 成功:返回一个新的套接字文件描述符(
sock
),用于服务器和客户端之间的通信。 - 失败:返回
-1
,并设置errno
指定错误码。
- 成功:返回一个新的套接字文件描述符(
6.运行服务器
void ServerIO(int sock)
{
char inbuffer[size];
while(true)
{
//使用read函数,将网络中传输的数据用sock文件描述符读取到inbuffer中,读取的大小是sizeof(inbuffer) - 1
ssize_t n = read(sock, inbuffer, sizeof(inbuffer) - 1);
if (n > 0)
{
//在最后一个位置添加0,用于停止
inbuffer[n] = 0;
cout << "服务端接收到数据 : " << inbuffer << endl;
string outbuffer = inbuffer;
cout << outbuffer << " server[echo]" << endl;
write(sock, outbuffer.c_str(), sizeof(outbuffer) - 1);
}
else if (n == 0) //这种情况是客户端关闭连接了
{
cout << "客户端关闭退出,服务端也退出..." << endl;
break;
}
}
close(sock);
}
read()函数
ssize_t read(int fd, void *buf, size_t count);
-
参数解释:
fd
:文件描述符,这里是通信套接字sock
。sock
指向服务器与某个客户端的连接。buf
:指向缓冲区的指针,用于存储读取到的数据。这里是inbuffer
。count
:要读取的最大字节数。代码中使用的是sizeof(inbuffer) - 1
。
-
返回值:
read()
函数返回读取的字节数。- 若返回
0
,表示对端关闭了连接(即客户端断开连接)。 - 若返回
-1
,表示发生了错误。
3.为什么 count 使用 sizeof(inbuffer) - 1
-
保留空间放置字符串终止符:
inbuffer
是一个char
数组,实际接收的内容是字符串(也即以\0
结尾的字符序列)。read()
函数不会自动添加字符串终止符\0
,因此我们需要在数据的末尾手动加上一个\0
。- 将
count
设置为sizeof(inbuffer) - 1
,让inbuffer
留出一个位置,以便在读取完数据后,在末尾加上\0
,构成有效的 C 字符串。
-
避免缓冲区溢出:
- 如果使用
sizeof(inbuffer)
作为count
的值,读取的数据可能会填满整个缓冲区,而没有为\0
留出空间,容易导致缓冲区溢出或后续操作产生不确定的行为。 sizeof(inbuffer) - 1
确保不会超过缓冲区的大小,从而避免溢出。
- 如果使用
Server启动
#include "TcpServer.hpp"
using namespace std;
void Usage(string proc)
{
cout << "服务端使用说明 : " << proc << " port" << endl;
}
//使用方式 --> ./tcpserver 8080
int main(int argc, char* argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 0;
}
//argc[1]是string类型,serverIp是uint16_t类型,需要用atoi()转换
uint16_t sercerIp = atoi(argv[1]);
unique_ptr<TcpServer> tser(new TcpServer(sercerIp)); //使用智能指针
tser->InitTcpServer();
tser->Start();
return 0;
}
client
client的启动方式是:./udpclient 127.0.0.0.1 8080 --> 格式就是./proc ip地址 port端口
1. 创建套接字
_sockfd = socket(AF_INET, SOCK_STREAM, 0);
2. 填充套接字
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = _serverport;
local.sin_addr.s_addr = inet_addr(_serverip.c_str());
这里的local
是套接字,里面填充的是服务端的ip地址和端口信息。
connect()函数
connect(_sockfd, (const struct sockaddr*)&local, len)
//模型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect()
函数的作用是让客户端发起一个连接请求,与服务器建立连接。此操作适用于TCP协议,在连接建立成功后,客户端可以通过 _sockfd
向服务器发送和接收数据。
sockfd
:- 这是客户端的套接字文件描述符,通常是由
socket()
函数创建的。 - 在调用
connect()
前,sockfd
并没有与具体的服务器地址关联,而connect()
将sockfd
连接到服务器的addr
地址。
- 这是客户端的套接字文件描述符,通常是由
addr
:- 这是一个指向
sockaddr
结构体的指针,用于指定服务器的 IP 地址和端口,即客户端希望连接的目标地址。 - 在代码中,
(const struct sockaddr*)&local
表示将local
(服务器的套接字地址信息)转换为sockaddr
类型。
- 这是一个指向
addrlen
:- 这是
addr
结构体的大小,以socklen_t
类型表示。在这里,sizeof(local)
被传入,以指定服务器地址结构体的长度。
- 这是
connect()
函数的返回值- 成功:返回
0
,表示连接成功,客户端与服务器之间的通信通道已建立。 - 失败:返回
-1
,表示连接失败,errno
被设置为相应的错误码。代码中如果连接失败会打印 “客户端连接失败…” 并退出。
- 成功:返回
3. 通过文件描述符向服务端发送信息
string message;
while(true)
{
cout << "Enter#";
getline(cin, message);
//通过sockfd,向服务端写数据,内容是message
write(_sockfd, message.c_str(), sizeof(message) - 1);
//可以通过read函数将服务端接收到的数据回显到服务端,这里就不作处理了
}
client启动
#include "TcpClient.hpp"
using namespace std;
void Usage(string proc)
{
cout << "客户端使用说明 : " << proc << " ip " << "port" << endl;
}
//输入是 : ./proc ip port
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 0;
}
string ip = argv[1];
uint16_t port = atoi(argv[2]);
unique_ptr<TcpClient> tclient(new TcpClient(ip, port));
tclient->InitTcpClient();
tclient->Start();
return 0;
}
github地址:
https://github.com/gaogo21/Linux-Learning/tree/master/NetWork/2024-10-29-tcp