重温
TCP的特点
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
创建套接字
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
//这里是面向字节流
因为之前介绍过UDP的接口,TCP和UDP相似。
从创建套接字,进行绑定都一模一样(除了SOCK_STREAM)
面向连接
UDP面向无连接,所以创建完套接字可以直接发送信息
TCP是面向连接的,所以发送信息前要先建立连接
服务器端:listen()
服务器是被连接的,要一直等待客户的连接。
需要linsten接口,来确定客户端有无发起连接
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
将一个已绑定地址的 TCP 套接字设为监听状态,用于接受客户端的连接请求
| 参数名 | 说明 |
|---|---|
sockfd | 由 socket() 创建,并用 bind() 绑定的套接字描述符 |
backlog | 系统允许排队的最大连接请求数量(连接队列长度) 内核维护两个队列: 🔹 半连接队列(握手未完成) 🔹 全连接队列(握手已完成,等待 accept()) |
| 返回值 | 说明 |
|---|---|
0 | 调用成功 套接字已进入监听状态 |
-1 | 调用失败 |
使用示例
n = ::listen(_sockfd, gbacklog);
if(n<0)
{
LOG(FATAL, "listen error\n");
exit(LISTEN_ERROR);
}
服务器端:accept()
在UDP中,创建完客户端即可接收数据
在TCP中,需要先建立连接
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
| 参数名 | 说明 |
|---|---|
sockfd | 监听套接字的文件描述符,必须是通过 socket() 创建,并使用 bind() + listen() 设置过的 |
addr | 客户端地址信息结构体指针(如 struct sockaddr_in*),接收客户端 IP 和端口,可为 NULL |
addrlen | 指向 socklen_t 类型的指针,传入 addr 的结构体大小,返回时填入实际地址大小 |
后两个参数和UDP的recvfrom一样是输出型参数,其指向客户端
| 返回值 | 说明 |
|---|---|
>=0 | 成功,返回一个新的已连接 socket 描述符,用于与客户端通信 |
-1 | 失败,连接未接收成功 |
其返回值是新的文件描述符(参考UDP返回值是0和-1)
使用示例
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = ::accept(_listensockfd, (struct sockaddr*)&peer, &len);
if (sockfd < 0)
{
LOG(WARNING, "accept error\n");
continue;
}
listen()和accept()的套接字
| 对比项 | listen 套接字 | accept 返回的套接字 |
|---|---|---|
| 作用 | 用于监听客户端连接请求,建立连接的“门户” | 用于与某个客户端进行数据通信 |
| 来源 | 通过 socket() + bind() + listen() 创建 | 由 accept() 从 listen 套接字中派生出来 |
| 是否用于通信 | 不能直接用于收发数据 | 可以用于 recv() / send() 与客户端通信 |
| 连接数量 | 一个 listen 套接字 可以接受多个连接请求 | 每调用一次 accept(),生成一个新连接套接字 |
| 生命周期 | 通常贯穿整个服务器进程,用于持续监听 | 客户端断开后就关闭,代表一次具体连接 |
| 示例变量名 | server_fd, listen_fd | client_fd, conn_fd |
| 是否阻塞 | 调用 listen() 不阻塞 | accept() 默认阻塞,直到有新连接 |
listen是看大门的,accept是接客的(
服务器端:read()
UDP是面向数据报的,读取的是报文
TCP是面向字节流的,可以用文件的读取和写入
值得关注的是read()在网络中的返回值
| 返回值 | 含义说明 |
|---|---|
> 0 | 实际读取到的字节数,表示成功读取到数据 |
== 0 | 表示连接已被对方关闭(对方调用了 close() 或 shutdown()) |
< 0 | 发生错误,读取失败,可通过 errno 获取具体错误信息 |
使用示例
while (true)
{
char inbuffer[1024];
ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
if (n > 0)
{
inbuffer[n] = '\0';
std::cout << inbuffer << std::endl;
}
else if (n == 0)
{
// client 退出且关闭了连接
LOG(INFO, "%s quit\n", clientaddr.c_str());
break;
}
else
{
LOG(ERROR, "read error\n");
break;
}
}
::close(sockfd);
客户端:connect()
向服务器发起连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
| 参数名 | 说明 |
|---|---|
sockfd | 客户端使用 socket() 创建的套接字描述符(必须是 SOCK_STREAM 类型) |
addr | 指向服务器地址结构体(如 struct sockaddr_in)的指针 |
addrlen | addr 结构体的大小,通常是 sizeof(struct sockaddr_in) |
| 返回值 | 含义说明 |
|---|---|
0 | 成功建立连接(TCP 三次握手完成) |
-1 | 连接失败,可通过 errno 查看具体原因 |
使用示例
struct sockaddr_in server;
int n = connect(sockfd, (sockaddr*)&server, sizeof(server));
if (n<0)
{
std::cerr << "connect error" << std::endl;
exit(3);
}
客户端:send()
客户端依然可以用read()和write()
send()用于字节流的发送
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
| 参数名 | 类型 | 说明 |
|---|---|---|
sockfd | int | 已连接的 socket 文件描述符(由 connect() 或 accept() 获得) |
buf | const void* | 指向要发送的数据缓冲区 |
len | size_t | 要发送的字节数 |
flags | int | 发送控制标志,常设为 0(可选其他如 MSG_DONTWAIT、MSG_NOSIGNAL) |
| 返回值 | 说明 |
|---|---|
> 0 | 实际成功发送的字节数(可能小于 len,需循环发送) |
== 0 | 理论上不会出现,若出现可能是连接已关闭 |
< 0 | 发送失败,返回 -1,可通过 errno 查看错误原因 |
使用示例
std::string outstring;
std::getline(std::cin, outstring);
ssize_t s = send(sockfd, outstring.c_str(), sizeof(outstring), 0);
if (s > 0)
{
char inbuffer[1024];
//recv用于接收数据
//ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t m = recv(sockfd, inbuffer, sizeof(inbuffer) - 1, 0);
if (m > 0)
{
inbuffer[m] = '\0';
std::cout << inbuffer << std::endl;
}
else
{
break;
}
}
else
{
break;
}
小结
介绍了TCP常用接口和使用。
2966

被折叠的 条评论
为什么被折叠?



