socket
创建套接字文件:
#include <sys/socket.h>
// 成功返回非负套接字描述符,失败返回-1
int socket(int domain, int type, int protocol);
domain值:
domain | 描述 |
---|---|
AF_INET | IPv4 Internet protocols |
AF_INET6 | IPv6 Internet protocols |
type值:
type | 描述 |
---|---|
SOCK_STREAM | Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. |
SOCK_DGRAM | Supports datagrams (connectionless, unreliable messages of a fixed maximum length). |
protocol值:
protocol | 描述 |
---|---|
IPPROTO_TCP | TCP协议 |
IPPROTO_UDP | UDP协议 |
socket里面并没有定义protocal的宏,需要额外引入<netinet/in.h>
sockaddr
sockaddr用来记录ip和端口信息,sockaddr是通用的结构体,没有具体划分ip和端口的信息字段,对于ipv4地址需要用sockaddr_in结构体,对于ipv6需要用sockaddr_in6结构体:
<sys/socket.h>
// 通用地址信息结构体
struct sockaddr {
sa_family_t sa_family; // 地址簇
char sa_data[14]; // 填充字符
};
<netinet/in.h>
// 存储ipv4地址
struct in_addr {
in_addr_t s_addr; // ipv4地址(网络序4字节)
};
// ipv4地址和端口信息
struct sockaddr_in {
sa_family_t sin_family; // AF_INET
in_port_t sin_port; // 端口号(网络序2字节)
struct in_addr sin_addr; // ipv4地址
char sin_zero[8]; // 填充字符
};
// 存储ipv6地址
struct in6_addr {
uint8_t s6_addr[16]; // ipv6地址(网络序16字节)
};
// ipv6地址和端口信息
struct sockaddr_in6 {
sa_family_t sin6_family; // AF_INET6
in_port_t sin6_port; // 端口号(网络序2字节)
uint32_t sin6_flowinfo; // 流信息
struct in6_addr sin6_addr; // ipv6地址
uint32_t sin6_scope_id; // 作用域的接口集合
};
在用sockaddr_in结构体之前,需要清零整个结构体。
将字符串转换为ip地址类型可以用inet_pton,转换成功返回1:
<netinet/in.h>
// 转换成功返回1,ip字符串不合法返回0,地址簇不支持返回-1
int inet_pton(int af, const char *src, void *dst);
除了通过字符串转换ip地址之外,<netinet/in.h>
头文件定义了INADDR_ANY
来表示0.0.0.0
这个通配地址。
bind
将socket套接字绑定到指定ip地址和端口上:
#include <sys/socket.h>
// 失败返回-1,成功返回0
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
对于服务器来说,如果要同时监听多张网卡,那么就需要绑定到INADDR_ANY
上,如果只接受本机访问,那么需要绑定到127.0.0.1
上。
listen
将服务器的套接字描述符状态从CLOSED
转移到LISTEN
:
#include <sys/socket.h>
// 失败返回-1,成功返回0
int listen(int sockfd, int backlog);
backlog
表示连接队列的大小,虽然标准中并没有说明,但BSD4.2的实现中,连接队列里面包括两种状态的连接:
- 一部分连接刚接收到SYN包,处于
SYN_RCVD
状态。 - 一部分连接已经完成了三次握手过程,处于
ESTABLISHED
状态
不能将backlog设置为0,因为这一行为标准未定义,如果不想客户端可以连接自己,那么需要关闭该监听套接字。并且实际的连接队列的大小不一定等于backlog
,比如有的实现会乘以一个系数1.5。
accept
用于接受连接队列中处于ESTABLISHED
状态的连接:
#include <sys/socket.h>
// 成功返回非负的连接套接字描述符,失败返回-1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
cliaddr可以用来获取客户端的地址和端口信息。
#include <arpa/inet.h>
// 转换成功返回dst指针,转换失败返回NULL
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
<netinet/in.h>
头文件定义了保存ipv4地址和ipv6字符串所需的字符数组长度:
#define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */
connect
客户端用来连接服务端:
#include <sys/socket.h>
// 失败返回-1,成功返回0
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
当客户端调用该接口的时候,就开始TCP三次握手,首先发送SYN包,若:
- 一直没有收到回复,那么会触发超时机制(75秒),在此期间会不断重发SYNC包,直到返回ETIMEOUT错误
- 如果服务主机返回RST(reset),那么表示服务主机上该端口没有服务在监听,那么客户端返回ECONNREFUSED错误
- 如果返回ICMP包,表示网络或主机不可达,那么还是会触发超时机制,在此期间会不断重发SYNC包,直到返回EHOSTUNREACH错误
客户端代码
#include <iostream>
#include <string>
#include <cstring> // memset strlen
#include <cstdint> // uint16_t
#include <cstdio> // snprintf
#include <sys/socket.h> // socket
#include <netinet/in.h> // IPPROTO_TCP htons
#include <arpa/inet.h> // inet_pton
#include <unistd.h> // write read
int main(int argc, char const *argv[])
{
// 创建连接套接字
int connectfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1 == connectfd) {
std::cout << "create connectfd failed" << std::endl;
return -1;
}
std::cout << "connectfd: " << connectfd << std::endl;
// 设置服务器地址(ipv4)
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr));
std::string servIP = "127.0.0.1";
uint16_t servPort = 23333;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(servPort);
int res = inet_pton(AF_INET, servIP.c_str(), &servAddr.sin_addr);
if (1 != res) {
std::cout << "presentation(" << servIP << ") to numeric failed" << std::endl;
return -1;
}
std::cout << "set server Address success" << std::endl;
// 连接服务器
res = connect(connectfd, reinterpret_cast<sockaddr *>(&servAddr), sizeof(servAddr));
if (-1 == res) {
std::cout << "connect server failed" << std::endl;
return -1;
}
std::cout << "connect server success" << std::endl;
// 体验echo服务
while (true) {
// 获取一行输入
std::string line;
getline(std::cin, line);
if ("quit" == line) {
break;
}
// 发送消息
ssize_t nLeft = line.size();
const char *curStr = line.c_str();
while (0 != nLeft) {
ssize_t nWrite = write(connectfd, curStr, nLeft);
if (nWrite < 0) {
std::cout << "write connectfd failed" << std::endl;
break;
}
nLeft -= nWrite;
curStr += nWrite;
}
if (0 != nLeft) {
break;
}
// 输出回复
constexpr int MAX = 1024;
char buf[MAX + 1];
ssize_t nRead = read(connectfd, buf, sizeof(buf));
if (nRead < 0) {
std::cout << "read connectfd failed" << std::endl;
break;
}
buf[nRead] = ' ';
std::cout << buf << std::endl;
}
// 关闭套接字
close(connectfd);
return 0;
}
服务端代码:
#include <iostream>
#include <string>
#include <cstring> // memset
#include <cstdint> // uint16_t
#include <sys/socket.h> // socket
#include <netinet/in.h> // IPPROTO_TCP htons ntohs INET_ADDRSTRLEN
#include <arpa/inet.h> // inet_pton inet_ntop
#include <unistd.h> // write read
int main(int argc, char const *argv[])
{
// 创建监听socket
int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1 == listenfd) {
std::cout << "create listenfd failed" << std::endl;
return -1;
}
std::cout << "listenfd: " << listenfd << std::endl;
// 设置服务器地址(ipv4)
sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr));
std::string servIP = "0.0.0.0";
uint16_t servPort = 23333;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(servPort);
int res = inet_pton(AF_INET, servIP.c_str(), &servAddr.sin_addr);
if (1 != res) {
std::cout << "presentation(" << servIP << ") to numeric failed" << std::endl;
return -1;
}
std::cout << "set server Address success" << std::endl;
// 绑定服务器地址
res = bind(listenfd, reinterpret_cast<sockaddr*>(&servAddr), sizeof(servAddr));
if (0 != res) {
std::cout << "bind listenfd to server address failed" << std::endl;
return -1;
}
std::cout << "bind listenfd to server address success" << std::endl;
// 套接字开启监听
res = listen(listenfd, 5);
if (0 != res) {
std::cout << "listen listenfd failed" << std::endl;
return -1;
}
std::cout << "listen listenfd success" << std::endl;
// 服务循环
while (true) {
// 接受远端连接
sockaddr_in clientAddr;
socklen_t len = sizeof(clientAddr);
int connectfd = accept(listenfd, reinterpret_cast<sockaddr*>(&clientAddr), &len);
if (-1 == connectfd) {
std::cout << "accept connectfd failed" << std::endl;
return -1;
}
char clientIP[INET_ADDRSTRLEN];
uint16_t clientPort = ntohs(clientAddr.sin_port);
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP));
std::cout << "accept connectfd(" << clientIP << ":" << clientPort << ") success" << std::endl;
// echo服务
while (true) {
// 接受消息
constexpr int MAX = 1024;
char buf[MAX + 1];
ssize_t nRead = read(connectfd, buf, sizeof(buf));
if (nRead < 0) {
std::cout << "read connectfd failed" << std::endl;
break;
} else if (0 == nRead) {
std::cout << "read EOF" << std::endl;
break;
}
buf[nRead] = ' ';
std::cout << buf << std::endl;
// echo消息
ssize_t nLeft = strlen(buf);
const char *curStr = buf;
while (0 != nLeft) {
ssize_t nWrite = write(connectfd, curStr, nLeft);
if (nWrite < 0) {
std::cout << "write connectfd failed" << std::endl;
break;
}
nLeft -= nWrite;
curStr += nWrite;
}
if (0 != nLeft) {
break;
}
}
// 结束本轮服务
close(connectfd);
}
return 0;
}