socket在很多应用常见下,单进程单线程,调用socket(),connect(),read(),write()等一套,对单个socket服务。
但在多socket场景下,这样的方法行不通了。server会在accept()时阻塞,等待client连入,那么到底是监听端口8080先accept(),还是监听端口8086先accept()。
这就需要引入非阻塞式轮询各个socket的状态,当他们连入状态改变时,在进入accept()。这样就可以在单个进程中,为多个socket服务。代码框架可以是
sock_A = socket(port_A);
sock_B = socket(port_B);
while(1){
if(is_ready){
accept(sock_A);
}
if(is_ready){
accept(sock_B);
}
}
Linux C中一切皆是文件,设置文件初始状态。当状态改变时,某些比特自动置位,就可以读取这些比特位,得知是否有事件触发了文件状态改变。这样的机制就是select,它的函数原型为
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
select()函数用来等待文件描述符状态的改变(可设定为阻塞与非阻塞)。n代表需要等待的文件描述符+1;参数readfds、writefds和exceptfds为描述符字,用来回传该描述词的读,写或异常的状况。
在使用select前,需要使用相关宏设定文件描述符等待的状态。宏函数包括
FD_CLR(int fd, fd_set* set); // 把fd在set中删除
FD_ISSET(int fd, fd_set* set); // 测试set中fd是否存在
FD_SET(int fd, fd_set* set); // 在set中设置fd
FD_ZERO(fd_set* set); // 将set清零
fd_set时select提供的一种数据结构,实际上时一个long型数组,每一个比特位与一个打开文件句柄建立联系。Linux下设置fd_set的描述字大小位1024 bits。这与用户级打开文件限制数对应(FD_SETSIZE,可以使用ulimit -n查看)。
fd_set set; // set为1024 bits,即128 bytes,以下例子仅展示低8位
FD_ZERO(&set); // 清除所有set位,此时set为0000 0000
FD_SET(0, &set); // 设置set中第0位,此时set为0000 0001
FD_CLR(1, &set); // 清楚set中第1位,如果此时set为0000 0010,则设置后为0000 0000
FD_ISSET(2, &set); // 测试第2位是否为1,如果此时set为0000 0100,则返回true
在来说下,为什么select需要设置n为需要等待文件描述符+1呢???
其实,select在说明文件解释,参数n需要设置为三个fd_set中最大文件描述符+1
nfds is the highest-numbered file descriptor in any of the three sets, plus 1.
言下之意,是内核为了提高select的查询效率,仅查询fd_set中的低ndfs位。
接下来说,最后一个参数timeout。struct timeval结构体定义为
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
select可以阻塞式等待,也可以非阻塞返回。设置timeval后,select会占用CPU等待。达到以下条件之一返回。
(1)文件描述符就绪
(2)被信号中断
(3)时间溢出
最后说下,select的返回值。成功时,返回三个文件描述符字中包含的文件描述符的数量(非0);如果超时,返回0;遇到错误返回-1。
---------------------------------------------------分割线---------------------------------------------------------
言归正传,回到使用select操作socket上来。
有了上面的基础,实现多个socket,简直是小小case了。
直接上代码。
client.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1){
cerr << "socket failed" << endl;
exit(1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_aton("127.0.0.1", &addr.sin_addr);
struct sockaddr_in self_addr;
self_addr.sin_family = AF_INET;
self_addr.sin_port = htons(10000);
inet_aton("127.0.0.1", &self_addr.sin_addr);
/* 客户端也可以绑定端口 */
int res = bind(sock, (struct sockaddr*)&self_addr, sizeof(self_addr));
if(res == -1){
cerr << "bind self failed" << endl;
exit(1);
}
int res = connect(sock, (struct sockaddr *)&addr,
sizeof(addr));
if(res == -1){
cerr << "connect failed" << endl;
exit(1);
}
char buf[100] = "呵呵哒,我是萌萌哒";
write(sock, buf, strlen(buf)+1);
read(sock, buf, sizeof(buf));
cout << buf << endl;
return 0;
}
server.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const static int client_port = 8080;
const static int monitor_port = 8090;
int main()
{
int client_sock = socket(AF_INET, SOCK_STREAM, 0);
if(client_sock == -1){
cerr << "socket failed" << endl;
exit(1);
}
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(client_port);
inet_aton("127.0.0.1", &client_addr.sin_addr);
int res = bind(client_sock, (struct sockaddr *)&client_addr,
sizeof(client_addr));
if(res == -1){
cerr << "bind failed" << endl;
exit(1);
}
listen(client_sock, 10);
int monitor_sock = socket(AF_INET, SOCK_STREAM, 0);
if(monitor_sock == -1){
cerr << "socket failed" << endl;
exit(1);
}
struct sockaddr_in monitor_addr;
monitor_addr.sin_family = AF_INET;
monitor_addr.sin_port = htons(monitor_port);
inet_aton("127.0.0.1", &monitor_addr.sin_addr);
res = bind(monitor_sock, (struct sockaddr *)&monitor_addr,
sizeof(monitor_addr));
if(res == -1){
cerr << "bind failed" << endl;
exit(1);
}
listen(monitor_sock, 10);
fd_set allset;
struct timeval tv = {0, 10000}; //set timeval(10ms)
while(1){
FD_ZERO(&allset);
FD_SET(client_sock, &allset);
FD_SET(monitor_sock, &allset);
select(client_sock+1, &allset, NULL, NULL, &tv);
select(monitor_sock+1, &allset, NULL, NULL, &tv);
if(FD_ISSET(client_sock, &allset)){
int clientfd = accept(client_sock, NULL, NULL);
pthread_t thread;
pthread_create(&thread, NULL, serv_client, (void *)&clientfd);
}
if(FD_ISSET(monitor_sock, &allset)){
int monitorfd = accept(monitor_sock, NULL, NULL);
pthread_t thread;
pthread_create(&thread, NULL, serv_monitor, (void *)&monitorfd);
}
}
return 0;
}
void *serv_client(void *arg)
{
int socketfd = *(int *)arg;
char buf[] = "萌萌哒,我是呵呵哒";
write(socketfd, buf, sizeof(buf));
return nullptr;
}
void *serv_monitor(void *arg)
{
cout << "一百块也不给我" << endl;
cout << "悄悄记下来" << endl;
int socketfd = *(int *)arg;
static struct sockaddr_in remote_addr;
socklen_t len = sizeof(remote_addr);
int ret = getsockname(socketfd, (struct sockaddr *)&remote_addr, &len); //get the monitorfd msg
if(ret != 0){
cerr << "getsockname failed" << endl;
}
cout << "IP: " << remote_addr.sin_addr << endl;
cout << "port: " <