创建服务器socket
在TCP服务器代码中,我们创建一个socket,然后调用bind函数,绑定到这个socket:
// 创建本地地址配置信息
struct addrinfo hints;
// 清空hints的东西,为设置新的信息做准备
memset(&hints,0,sizeof(hints));
// TCP,选用SOCK_STREAM
hints.ai_socktype = SOCK_STREAM;
// 因为此地址将用作监听socket的地址,因此设置被动状态
hints.ai_flags = AI_PASSIVE;
// 返回的本地地址信息
struct addrinfo *local_address;
// getaddrinfo函数初始化本地地址信息,端口设置为8899
if(getaddrinfo(NULL,"8899",&hints,&local_address)){
fprintf(stderr,"getaddrinfo() failed. (%d)\n",errno);
return 1;
}
printf("Creating a socket...\n");
// 使用配置好的本地地址信息创建socket
int server_socket = socket(local_address->ai_family,local_address->ai_socktype,local_address->ai_protocol);
if(server_socket == -1){
fprintf(stderr,"socket() failed. (%d)\n",errno);
return 1;
}
我们没有指定hints.ai_family = AF_INET或 AF_INET6,因为getaddrinfo可以动态决定它的具体类型,也就是我们可以同时兼容IPv4 和IPv6.
绑定socket到一个本地地址
上一步只是用本地地址信息创建了socket,还要将这个socket与本地地址绑定起来,才能真正关联起来。由bind函数来完成
printf("Binding socket to local address...\n");
if(bind(server_socket,local_address->ai_addr,local_address->ai_addrlen)){
fprintf(stderr,"bind() failed. (%d)\n",errno);
return 1;
}
// 绑定完成后,本地地址信息后面就不会再使用了,于是我们释放掉它,节省内存
freeaddrinfo(local_address);
让socket进入监听状态
调用listen函数让socket可以监听外界对它的访问。我们这里设置了最多有10个等待处理的进来的访问,换句话说,
printf("Listening...\n");
if(listen(server_socket,10) == -1){
fprintf(stderr,"listen() failed. (%d)\n",errno);
return 1;
}
使用select函数处理socket的同步
这一段是最精彩的。
// 初始化fd_set文件描述符集合
fd_set master_set;
// 清空文件描述符集合里的东西
FD_ZERO(&master_set);
// 将刚刚创建的用于监听的socket加入到集合中,当这个socket需要处理进来的请求时,就可以select函数中返回
FD_SET(server_socket,&master_set);
int fdmax;
fdmax = server_socket;
// 无限循环,这是正常的,因它是主程序,不能够退出。
while(1){
// 定义一个读文件描述符集合
fd_set read_fds;
// 清0集合
FD_ZERO(&read_fds);
// 将master_set复制一份到read_fds
memcpy(&read_fds,&master_set,sizeof(master_set));
// select函数将read_fds传进去,第一个参数是最大socket编号加1,read_fds包含所有需要监听读变化的文件描述符,如果有变化就会通过read_fds返回(只包含有读变化的socket,进去是全部socket,出来时只有部分),所以前面需要将master_set复制一份到read_fds。select是一个阻塞的函数,除非有变化,否则就卡在这了,这样省了很多资源的。
if(select(fdmax+1,&read_fds,0,0,0) == -1){
fprintf(stderr,"select() failed. (%d)\n",errno);
return 1;
}
// 来到这一步,说明有变化了,遍历一遍socket
for(int i = 0;i <= fdmax;i++){
// 检查socket是否在返回的读文件描述符集合中
if(FD_ISSET(i,&read_fds)){
// 如果在读文件描述符集合中,那么看看是否是监听用的socket,即服务端socket
if(i == server_socket){
// 看来是有新的访问要建立连接,处理新连接
// 记录对端(客户端)socket的信息
struct sockaddr_storage peer_address;
socklen_t peer_address_size = sizeof(peer_address);
// accept函数创建对端(客户端)socket
int peer_socket = accept(server_socket,(struct sockaddr*)&peer_address,&peer_address_size);
if(peer_socket == -1){
fprintf(stderr,"accept() failed. (%d)\n",errno);
return 1;
}
// 将客户端socket放入文件描述符集合中,以便与其通信
FD_SET(peer_socket,&master_set);
if(peer_socket > fdmax){
fdmax = peer_socket;
}
char address_buffer[100];
// 打印客户端的信息
getnameinfo((struct sockaddr*)&peer_address,peer_address_size,address_buffer,sizeof(address_buffer),0,0,NI_NUMERICHOST);
printf("Accepted connection on descriptor %d (host=%s)\n",peer_socket,address_buffer);
}
else{
//处理客户端来的信息
char read[1024]; // 准备一个接收信息字符数组
// 接收客户端的数据
int bytes_read = recv(i,read,sizeof(read),0);
if(bytes_read <= 0){
// 如果接收到数据小于或等于0,那么意味着客户端要关闭
//connection closed by client
printf("Terminated connection on descriptor %d\n",i);
// 将客户端socket从master_set中移除
FD_CLR(i,&master_set);
// 在服务端这边关闭客户端socket
close(i);
continue;
}
printf("Received message (%d bytes): %s\n",bytes_read,read);
///
//echo message back to client
for (int j = 0; j < bytes_read; j++)
{
read[j] = toupper(read[j]);
}
printf("Sending message (%d bytes): %s\n",bytes_read,read);
// 向客户端发送数据
send(i,read,bytes_read,0);
}
}
}
}
聊天室
///
//echo message back to client
for (int j = 0; j < bytes_read; j++)
{
read[j] = toupper(read[j]);
}
printf("Sending message (%d bytes): %s\n",bytes_read,read);
// 向客户端发送数据
send(i,read,bytes_read,0);
将上述代码,用下面的代码代替,就可以将程序变成聊天室。聊天室就要将信息发给每一个客户端,服务端和自己是不需要收到发的信息的。
for(int j = 0; j <= fdmax; j++){
// 检查socket中在不在master_set
if(FD_ISSET(j,&master_set)){
// 如果是服务端socket则进入下一个
if(j == server_socket){
continue;
}
// 如果是自己,即同一个socket则进入下一轮
if(j == i){
continue;
}
// 给其他客户端发信息
send(j,read,bytes_read,0);
}
}
完整代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(int argc,char *argv[]){
printf("Configuring local address...\n");
struct addrinfo hints;
memset(&hints,0,sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
struct addrinfo *local_address;
if(getaddrinfo(NULL,"8080",&hints,&local_address)){
fprintf(stderr,"getaddrinfo() failed. (%d)\n",errno);
return 1;
}
printf("Creating a socket...\n");
int server_socket = socket(local_address->ai_family,local_address->ai_socktype,local_address->ai_protocol);
if(server_socket == -1){
fprintf(stderr,"socket() failed. (%d)\n",errno);
return 1;
}
printf("Binding socket to local address...\n");
if(bind(server_socket,local_address->ai_addr,local_address->ai_addrlen)){
fprintf(stderr,"bind() failed. (%d)\n",errno);
return 1;
}
freeaddrinfo(local_address);
printf("Listening...\n");
if(listen(server_socket,10) == -1){
fprintf(stderr,"listen() failed. (%d)\n",errno);
return 1;
}
fd_set master_set;
FD_ZERO(&master_set);
FD_SET(server_socket,&master_set);
int fdmax;
fdmax = server_socket;
while(1){
fd_set read_fds;
FD_ZERO(&read_fds);
memcpy(&read_fds,&master_set,sizeof(master_set));
if(select(fdmax+1,&read_fds,0,0,0) == -1){
fprintf(stderr,"select() failed. (%d)\n",errno);
return 1;
}
for(int i = 0;i <= fdmax;i++){
if(FD_ISSET(i,&read_fds)){
if(i == server_socket){
//handle new connection
struct sockaddr_storage peer_address;
socklen_t peer_address_size = sizeof(peer_address);
int peer_socket = accept(server_socket,(struct sockaddr*)&peer_address,&peer_address_size);
if(peer_socket == -1){
fprintf(stderr,"accept() failed. (%d)\n",errno);
return 1;
}
FD_SET(peer_socket,&master_set);
if(peer_socket > fdmax){
fdmax = peer_socket;
}
char address_buffer[100];
getnameinfo((struct sockaddr*)&peer_address,peer_address_size,address_buffer,sizeof(address_buffer),0,0,NI_NUMERICHOST);
printf("Accepted connection on descriptor %d (host=%s)\n",peer_socket,address_buffer);
}
else{
//handle data from client
char read[1024];
int bytes_read = recv(i,read,sizeof(read),0);
if(bytes_read <= 0){
//connection closed by client
printf("Terminated connection on descriptor %d\n",i);
FD_CLR(i,&master_set);
close(i);
continue;
}
printf("Received message (%d bytes): %s\n",bytes_read,read);
//echo message back to client
// for (int j = 0; j < bytes_read; j++)
// {
// read[j] = toupper(read[j]);
// }
// printf("Sending message (%d bytes): %s\n",bytes_read,read);
// send(i,read,bytes_read,0);
for(int j = 0; j <= fdmax; j++){
if(FD_ISSET(j,&master_set)){
if(j == server_socket){
continue;
}
if(j == i){
continue;
}
send(j,read,bytes_read,0);
}
}
}
}
}
}
printf("Closing socket\n");
close(server_socket);
printf("Finished.\n");
return 0;
}