socket网络编程是每个服务端开发人员必会技能,但是目前市面上各种web服务器容器,屏蔽了很多底层实现,导致很多socket通信细节被屏蔽,本文结合在linux下C语言socket通信说明一下网络通信的一些注意点。
目录
1.多进程模型tcp服务器
2.参数SO_REUSEADDR使用
3.参数SO_REUSEPORT使用
4.nginx中参数SO_REUSEADDR和SO_REUSEPORT的使用
以下代码在主进程中bind,listen监听套接字,然后fork两个子进程,子进程中进行accept等待客户端连接,主进程关闭监听套接字。子进程中将接收到客户端的信息打印并返回给客户端。客户端使用网络测试工具nc。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int port = 8090;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == lfd) {
perror("scoket");
return -1;
}
int ret = 0;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
if (-1 == ret) {
perror("bind");
return -1;
}
ret = listen(lfd, 128);
char buf[8192] = {0};
int len = 0;
int total = 0;
char tmpBuf[1024] = {0};
struct sockaddr_in cliaddr;
socklen_t socketlen =sizeof(cliaddr);
pid_t pro_cli;
signal(SIGCHLD, SIG_IGN);
std::cout << "main process: " << getpid() << std::endl;
for (int i = 0; i < 2; i++) {
pro_cli = fork();
if (pro_cli > 0) {
std::cout << "child process: " << pro_cli << std::endl;
}
if (0 == pro_cli) {
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);
std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
while (true) {
if (-1 == cfd) {
perror("accept");
return -1;
}
memset(buf, 0x00, sizeof(buf));
memset(tmpBuf, 0x00, sizeof(tmpBuf));
total = 0;
if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {
if (total + len < sizeof(buf)) {
memcpy(buf + total, tmpBuf, len);
}
send(cfd, buf, len, 0);
}
if ('\0' != buf[0]) {
std::cout << buf << std::endl;
}
if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕
//
}
else if (0 == len) { //客户端断开了连接
std::cout << "close client" << std::endl;
close(cfd);
}
else {
//perror("recv");
}
}
close(cfd);
close(lfd);
exit(0);
}
}
close(lfd);
sleep(999999999);
std::cout << "main end" << std::endl;
return 0;
}
运行效果如下:
说明:如上显示创建三个进程,主进程17973,两个子进程17974和17975。开启两个nc客户端去连接服务器,并分别发送信息给服务器,服务器回显nc发送的消息。
关于close_wait状态:
(1)只会出现在被动关闭端
(2)状态一直停留在close_wait状态,而没有发送FIN信号并迁移到LAST_ACK状态,一般都是在收到主动关闭发起方的FIN信号后没有调用进程的close方法导致状态不迁移
(3)close_wait状态是不正常的,一般是由于bug产生,应该避免
ctrl + C杀死服务器进程时,再重启服务端发生地址已经使用错误。如下所示
服务端是主动关闭的一方,其处于FIN_WAIT2状态,再关闭nc客户端后,其状态为TIME_WAIT,所以服务端不能在此启动。
如何能让服务端进程退出后立马能够在此监听端口呢?
int opt = -1;
int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
完整代码如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int port = 8090;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == lfd) {
perror("scoket");
return -1;
}
int ret = 0;
int opt = -1;
ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
if (-1 == ret) {
perror("bind");
return -1;
}
ret = listen(lfd, 128);
char buf[8192] = {0};
int len = 0;
int total = 0;
char tmpBuf[1024] = {0};
struct sockaddr_in cliaddr;
socklen_t socketlen =sizeof(cliaddr);
pid_t pro_cli;
signal(SIGCHLD, SIG_IGN);
std::cout << "main process: " << getpid() << std::endl;
for (int i = 0; i < 2; i++) {
pro_cli = fork();
if (pro_cli > 0) {
std::cout << "child process: " << pro_cli << std::endl;
}
if (0 == pro_cli) {
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);
std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
while (true) {
if (-1 == cfd) {
perror("accept");
return -1;
}
memset(buf, 0x00, sizeof(buf));
memset(tmpBuf, 0x00, sizeof(tmpBuf));
total = 0;
if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {
if (total + len < sizeof(buf)) {
memcpy(buf + total, tmpBuf, len);
}
send(cfd, buf, len, 0);
}
if ('\0' != buf[0]) {
std::cout << buf << std::endl;
}
if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕
//
}
else if (0 == len) { //客户端断开了连接
std::cout << "close client" << std::endl;
close(cfd);
}
else {
//perror("recv");
}
}
close(cfd);
close(lfd);
exit(0);
}
}
close(lfd);
sleep(999999999);
std::cout << "main end" << std::endl;
return 0;
}
如上所示,端口处于LISTEN状态,再其动一个服务端程序,出现地址已经使用错误,如何在端口已经监听的情况下再次监听端口呢?
setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
完整代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
int port = 8090;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == lfd) {
perror("scoket");
return -1;
}
int ret = 0;
int opt = -1;
ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (-1 == ret) {
perror("setsockopt");
return -1;
}
setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
if (-1 == ret) {
perror("setsockopt");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
if (-1 == ret) {
perror("bind");
return -1;
}
ret = listen(lfd, 128);
char buf[8192] = {0};
int len = 0;
int total = 0;
char tmpBuf[1024] = {0};
struct sockaddr_in cliaddr;
socklen_t socketlen =sizeof(cliaddr);
pid_t pro_cli;
signal(SIGCHLD, SIG_IGN);
std::cout << "main process: " << getpid() << std::endl;
for (int i = 0; i < 2; i++) {
pro_cli = fork();
if (pro_cli > 0) {
std::cout << "child process: " << pro_cli << std::endl;
}
if (0 == pro_cli) {
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &socketlen);
std::cout << "process::" << getpid() << " client: " << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << std::endl;
while (true) {
if (-1 == cfd) {
perror("accept");
return -1;
}
memset(buf, 0x00, sizeof(buf));
memset(tmpBuf, 0x00, sizeof(tmpBuf));
total = 0;
if ((len = recv(cfd, tmpBuf, sizeof(tmpBuf), 0)) > 0) {
if (total + len < sizeof(buf)) {
memcpy(buf + total, tmpBuf, len);
}
send(cfd, buf, len, 0);
}
if ('\0' != buf[0]) {
std::cout << buf << std::endl;
}
if (-1 == len && errno == EAGAIN && total > 0) { //接收数据完毕
//
}
else if (0 == len) { //客户端断开了连接
std::cout << "close client" << std::endl;
close(cfd);
}
else {
//perror("recv");
}
}
close(cfd);
close(lfd);
exit(0);
}
}
close(lfd);
sleep(999999999);
std::cout << "main end" << std::endl;
return 0;
}
网络状态和运行状态如下: