这篇文章记录了实现一个简易点对点聊天程序的过程,软件开发比较好的地方就是能够看到许多日常事务的本质,但也导致出去玩的时候会没有惊奇感,不过知道更多的未知的才会更有意思吧!
setsockopt
服务器端尽可能使用SO_REUSEADDR
。
在绑定之前尽可能调用setsockopt
来设置SO_REUSEADDR
套接字选项。
使用SO_REUSEADDR
选项可以使得不必等待TIME_WAIT
状态消失就可以重启服务器。
// 功能:获取或者设置与某个套接字关联的选项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该将层的值指定为SOL_SOCKET。为了操作其他层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议号TCP.
#include /* See NOTES */
#include
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数1:sockfd 标识一个套接口的描述字
参数2:level 选项定义的层次; 支持SOL_SOCKET IPPROTO_TCP IPPROTO_IP IPPROTO_IPV6
参数3:optname 需设置的选项
参数4:optval 指针 指向存放选项待设置的新值的缓冲区
参数5:optlen optval缓冲区长度
返回值:若无错误发生,setsockopt()返回0.否则的话,返回SOCKET_ERROR(-1)。
当level为SOL_SOCKET时,比较常用的设置选项如下:
1. SO_RCVBUF和SO_SNDBUF:用于设置/读取发送缓冲区(SO_RCVBUF)和接收缓冲区大小(SO_SNDBUF),选项值类型:int,指定新的缓冲区大小,对setsockopt和getsockopt有效。
说明:设置缓冲区大小只能在TCP连接建立之前进行,TCP将接收缓冲区大小用于流量控制,UDP不提供流量控制,UDP没有实际的发送缓冲区,设置发送缓冲区的大小将改变能发送的最大UDP数据报的大小。使用如下:
int rcv_buf_size = 32*1024, snd_buf_size = 32*1024;
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcv_buf_size, sizeof(int));
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &SND_buf_size, sizeof(int));
2. SO_REUSEADDR:用于复用socket地址(端口号),选项值类型:int 0-不能复用 1-可以复用,默认值为0,对setsockopt和getsockopt有效。
复用地址一般用于下列情况:
a.快速启动服务器:服务器在有客户端连接时终止,然后立即重启,由于TIME_WAIT状态,会导致原来的socket依然存在,bind操作将失败,如果指定SO_RUSEADDR选项可以避免这个问题。
b.启动一个服务器程序的多个实例:当使用IP别名的时候,可以将若干个服务程序使用不同IP绑定到同一端口上。
c.多个socket绑定同一端口:用于UDP程序在有多个网络接口时,为了区分数据报来自哪一个网络接口而使用。
举例:
int on = 1;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
ERR_EXIT("setsockopt");
}
3. SO_KEEPALIVE:用于TCPsocket检测/保持网络连接,这个选项被设置后,2小时以内双方没有数据交互,将发送一个探测数据段到对方,此时可能出现以下情况:
a. 得到对方正确响应,继续等待下一次2小时超时;
b. 收到RST数据段 返回错误ECONNRESET
c. 对方无响应 多次发送探测数据段直到超时返回错误ETIMEOUT
d. 选项值类型 int 0-不能发送 1-可以发送 默认值为0 对setsockopt和getsockopt有效
fork
函数一个进程,包括代码、数据和分配给进程的资源。fork()
函数通过系统调用创建一个原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()
函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void handler(int sig)
{
printf("recv a sig=%d\n", sig);
exit(EXIT_SUCCESS);
}
int main(void)
{
int count = 0;
pid_t pid;
pid = fork();
if(-1 == pid)
{
ERR_EXIT("fork");
}
if(0 == pid)
{
signal(SIGUSR1, handler);
printf("this is child process %d\n", getpid());
count++;
}
else
{
printf("this is father process %d\n", getpid());
count++;
}
printf("count=%d\n", count);
return 0;
}
// 结果输出:
this is father process 12842
count=1
this is child process 12843
count=1
在语句pid = fork();
之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程几乎完全相同,我们可以根据fork()
的返回值来在两个进程中执行不同的任务。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
fork
返回新创建子进程的进程ID
;fork
返回0;fork
返回一个负值。所以,在fork之后,count变量分别存在于子进程和父进程中,互不干扰。
在fork
函数执行完毕后,如果创建进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork
函数返回0;在父进程中,fork
返回子进程的进程ID
。我们可以通过fork
返回的值来判断当前进程是子进程还是父进程。
其实就相当于链表,父进程返回的pid
指向子进程的进程id
,因为子进程没有子进程,所以其pid
为0。
fork
出错可能有两种原因:
errno
的值被设置为EAGAIN
。errno
的值被设置为ENOMEM
。Process ID
),可以通过getpid()
函数获得,还有一个记录父进程pid
的变量,可以通过getppid()
函数获得变量的值。更为详细的参考linux中fork()函数详解,这篇博客总结的非常好!
#include
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void handler(int sig)
{
printf("recv s sig=%d\n", sig);
exit(EXIT_SUCCESS);
}
int main(void)
{
int sock;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock <0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器端地址
if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
ERR_EXIT("connect");
}
pid_t pid;
pid = fork();
if(-1 == pid)
{
ERR_EXIT("fork");
}
else if(0 == pid)
{
char recvBuf[1024];
while(1)
{
memset(recvBuf, 0, sizeof(recvBuf));
int ret = read(sock, recvBuf, sizeof(recvBuf));
if(-1 == ret)
{
ERR_EXIT("read");
}
else if(0 == ret)
{
printf("peer close\n");
break;
}
fputs(recvBuf, stdout);
}
close(sock);
kill(getppid(), SIGUSR1);
}
else
{
signal(SIGUSR1, handler);
char sendBuf[1024] = {0};
while(fgets(sendBuf, sizeof(sendBuf), stdin) != NULL)
{
write(sock, sendBuf, strlen(sendBuf));
memset(sendBuf, 0, sizeof(sendBuf));
}
close(sock);
}
return 0;
}
#include
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void handler(int sig)
{
printf("recv a sig=%d\n", sig);
exit(EXIT_SUCCESS);
}
/********
1.问题:不能处理多个客户端连接服务器
********/
int main(void)
{
int listenfd;
listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd <0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定本机的任意地址 网络字节序
// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// inet_aton("127.0.0.1", &servaddr.sin_addr);
// 没有这个会出现bind: Address already in use
#if 1
int on = 1;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
ERR_EXIT("setsockopt");
}
#endif
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
ERR_EXIT("bind");
}
if(listen(listenfd, SOMAXCONN) < 0)
{
ERR_EXIT("listen");
}
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
// 原因在于服务器端无法接收第二个客户端的连接请求,第一次请求之后就直接进入了和第一个客户端响应的程序中
if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0) // conn表示已连接套接字
{
ERR_EXIT("accept");
}
printf("ip=%s, port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid_t pid;
pid = fork();
if(-1 == pid)
{
ERR_EXIT("fork");
}
if(0 == pid)
{
// 通知子进程关闭
signal(SIGUSR1, handler);
char sendBuf[1024] = {0};
while(fgets(sendBuf, sizeof(sendBuf), stdin) != NULL)
{
write(conn, sendBuf, strlen(sendBuf));
memset(sendBuf, 0, sizeof(sendBuf));
}
exit(EXIT_SUCCESS);
}
else
{
char recvBuf[1024];
while(1)
{
memset(recvBuf, 0, sizeof(recvBuf));
int ret = read(conn, recvBuf, sizeof(recvBuf));
if(-1 == ret)
{
ERR_EXIT("read");
}
else if(0 == ret)
{
printf("peer close\n");
break;
}
fputs(recvBuf, stdout);
}
kill(pid ,SIGUSR1);
exit(EXIT_SUCCESS);
}
close(conn);
close(listenfd);
return 0;
}
所有的学习都贵在坚持!!!
setsockopt()函数功能介绍
setsockopt和getsockopt函数解析
linux中fork()函数详解