目录
使用setsockopt设置&getsockopt获取网络属性
网络超时检测概念
详情见:使用select函数实现超时检测
setsockopt、getsockopt
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);
参数:
sockfd:套接字
level:选项的级别
SOL_SOCKET 套接字级别!!!!!!!!!!!
IPPROTO_IP IP级别
SOL_PACKET 原始套接字
optname:选项的名称
不同的级别有不同的选项
对于套接字API级别,常见的选项
SO_BROADCAST 是否允许发送广播
SO_RCVBUF 接收缓冲区大小(bytes)
SO_SNDBUF 发送缓冲区大小(bytes)
SO_REUSEADDR 端口复用
SO_RCVTIMEO 接收超时时间 使用 timeval 结构体
SO_SNDTIMEO 发送超时时间 使用 timeval 结构体
如果超时,错误码会被设置成 EAGAIN !!!!!!!!!!!!
optval:选项的值
没有特殊说明的情况下,该参数是一个 int * 指针
接收超时时间 使用 timeval 结构体:
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 */
};
optlen:
optval的大小
返回值:
成功 0
失败 -1
使用多线程实现TCP并发服务器
无超时检测版:多线程实现TCP并发
recv — 在套接字中接收一条消息
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
sockfd:
在哪个套接字中接收
buf:
存放要接收消息的缓冲区的首地址
len:
想要接受的数据的字节数
flags:
0 阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功 实际读到的字节数
失败 -1
如果对方的socket已经关闭了,recv会返回0 !!!!!!!!!!!!
send — 向套接字中发送一条消息
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
sockfd:
向哪个套接字发
buf:
要发送的数据的首地址
len:
想要发送的数据的字节数
flags:
0 阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功 实际发送的字节数
失败 -1
如果对方的socket已经关闭了,第一次send会返回0
第二次send会报错 SIGPIPE !!!!!!!!!!!
代码实现
服务器—01server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
/*
#include <sys/time.h>*/
#include <errno.h>
#define ERRLOG(errmsg) \
do \
{ \
printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
typedef struct
{
int accept_fd;
struct sockaddr_in client_addr;
} num_t;
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);
//子线程处理
void *task1(void *arg);
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int sockfd = socket_bind_listen(argv);
//用来保存客户端信息的结构体
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
//传递信息的结构体
num_t ku;
memset(&ku, 0, sizeof(ku));//清空
//创建线程
pthread_t tid;
while (1)
{
//由已经设置过超时时间的sockfd产生的acceptfd也会继承这个超时属性
//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
ku.accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
if (-1 == ku.accept_fd)
{
if(errno == EAGAIN){
//5秒没有新的客户端连接
printf("accept 5秒超时...\n");
continue;
}
else
{
ERRLOG("accept error");
}
}
printf("客户端 (%s:%d) 连接了\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
//创建子线程处理新客户端的读写请求
//需要将该客户端的 acceptfd 和 网络信息结构体传给子线程
ku.client_addr = client_addr;
if (pthread_create(&tid, NULL, task1, (void *)&ku))
ERRLOG("create tid2 error");
//设置线程分离属性,无需手动回收资源
//分离态的线程结束后资源会被自动回收,这个函数不会阻塞
pthread_detach(tid);
}
//关闭监听套接字 一般不关闭
close(sockfd);
return 0;
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[])
{
// 1.创建套接字 //IPV4 //TCP
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
ERRLOG("socket error");
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); //清空
server_addr.sin_family = AF_INET; // IPV4
//端口号 填 8888 9999 6789 ...都可以
// atoi字符串转换成整型数
//将无符号2字节整型 主机-->网络
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
//字符串转换成32位的网络字节序二进制值
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
/*----------------------------------------------------------------------------*/
//设置端口复用
int on_off= 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on_off, sizeof(on_off)))
ERRLOG("setsockopt error");
/*----------------------------------------------------------------------------*/
//设置超时检测时间 5s
struct timeval tm;
tm.tv_sec = 5; //秒
tm.tv_usec = 0; //微秒
//设置套接字的网络属性 /套接字级别 /接收超时时间
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)))
ERRLOG("setsockopt error");
/*----------------------------------------------------------------------------*/
// 3.将套接字和网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("bind error");
//将套接字设置成被动监听状态
if (-1 == listen(sockfd, 10))
ERRLOG("listen error");
return sockfd;
}
//子线程处理
void *task1(void *arg)
{
//线程的处理函数
num_t ku = *(num_t *)arg;
char buff[128] = {0}; //临时存放数据
int ret = 0;
//接收客户端发来的数据
while (1)
{
//由已经设置过超时时间的sockfd产生的acceptfd也会继承这个超时属性
//也就是说,客户端5秒没有给服务器发消息 也会报错 recv超时
//如果想使用相同的时间,直接使用即可;如果不想使用这个时间,可以再次调用setsockopt重新设置
if (0 > (ret = recv(ku.accept_fd, buff, sizeof(buff), 0)))
{
if (errno == EAGAIN)
{
//客户端5秒没有给服务器发数据
printf("recv接收,超时...\n");
//可以踢掉该客户端 break之后关闭套接字即可
break;
}
ERRLOG("recv error");
}
else if (0 == ret) //客户端侧CTRL+C
{
printf("客户端 (%s:%d) 断开连接\n",
inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
break;
}
else
{
if (0 == strcmp(buff, "quit"))
{
printf("客户端 (%s:%d) 退出了\n",
inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port));
break;
}
printf("客户端 (%s:%d) 发来数据:[%s]\n",
inet_ntoa(ku.client_addr.sin_addr), ntohs(ku.client_addr.sin_port), buff);
//组装回复给客户端的应答
strcat(buff, "---996");
//回复应答
if (0 > (ret = send(ku.accept_fd, buff, sizeof(buff), 0)))
ERRLOG("send error");
}
}
//关闭该客户端的套接字
close(ku.accept_fd);
//线程退出
pthread_exit(NULL);
}
客户端—02client.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERRLOG(errmsg) \
do \
{ \
printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
perror(errmsg); \
exit(-1); \
} while (0)
int main(int argc, const char *argv[])
{
//检测命令行参数个数
if (3 != argc)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
exit(-1);
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
ERRLOG("socket error");
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
//端口号 填 8888 9999 6789 ...都可以
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
//与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("connect error");
printf("---连接服务器成功---\n");
char buff[128] = {0};
while (1)
{
scanf("%s", buff);
int ret = 0;
if (0 > (ret = send(sockfd, buff, sizeof(buff), 0)))
ERRLOG("send error");
if (0 == strcmp(buff, "quit"))
break;
if (0 > (ret = recv(sockfd, buff, sizeof(buff), 0)))
{
ERRLOG("recv error");
}
else if(0==ret)
{
//说明超时了 服务器已经把客户端踢掉了
//客户端5秒没有给服务器发数据
//服务器break之后关闭套接字
printf("由于你长时间没有说话,服务器已经把你踢掉了...\n");
break;
}
printf("收到服务器回复:[%s]\n", buff);
}
//关闭套接字
close(sockfd);
return 0;
}
执行结果
5秒不连接、不发数据,服务器端现象
5秒不发数据,客户端现象
注意
使用setsockopt设置套接字的网络属性
应该插放在 socket创建套接字 - bind将套接字和网络信息结构体绑定 之间
1.创建套接字
socket()
2.填充服务器网络信息结构体
struct sockaddr_in
----------------------------------------------------------
3.设置超时检测时间 5s
struct timeval tm;
tm.tv_sec = 5; //秒
tm.tv_usec = 0; //微秒
4.设置套接字的网络属性 /套接字级别 /接收超时时间
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)))
ERRLOG("setsockopt error");
----------------------------------------------------------
5.将套接字和网络信息结构体绑定
bind()
使用setsockopt设置端口复用
应该插放在 socket创建套接字 - bind将套接字和网络信息结构体绑定 之间
1.创建套接字
socket()
2.填充服务器网络信息结构体
struct sockaddr_in
----------------------------------------------------------
3.设置超时检测时间 5s
int on_off= 1;
4.设置端口复用 /套接字级别 /端口复用
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on_off, sizeof(on_off)))
ERRLOG("setsockopt error");
----------------------------------------------------------
5.将套接字和网络信息结构体绑定
bind()