筆者這學期在進行Linux系統的學習,做了一些有意思的程序分享給大家,並通過這種方式把它記錄下來。
實現的思路是服務器端每當接受一個socket連接請求後,就將該連接的套接字描述符存入一個數組中,以此識別每個socket連接。並根據客戶端發送信息判斷後執行相應的命令,比如:展示當前在線客戶端信息、羣聊、私聊等。
Client.c
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#define MAX_SIZE 1024
static void usage(const char* proc)
{
printf("%s [ip] [port]\n", proc);
}
int main(int argc, const char * argv[])
{
if(argc != 3)
{
usage(argv[0]);
exit(1);
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
bzero(&(server_addr.sin_zero), 8);
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock_fd == -1)
{
perror("socket error");
return 1;
}
char recv_msg[MAX_SIZE];
char input_msg[MAX_SIZE];
if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)
{
fd_set client_fd_set;
struct timeval tv;
while(1)
{
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&client_fd_set);
FD_SET(STDIN_FILENO, &client_fd_set);
FD_SET(server_sock_fd, &client_fd_set);
select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);
if(FD_ISSET(STDIN_FILENO, &client_fd_set))
{
bzero(input_msg, MAX_SIZE);
fgets(input_msg, MAX_SIZE, stdin);
if(send(server_sock_fd, input_msg, MAX_SIZE, 0) == -1)
perror("發送消息出錯!\n");
}
if(FD_ISSET(server_sock_fd, &client_fd_set))
{
bzero(recv_msg, MAX_SIZE);
long byte_num = recv(server_sock_fd, recv_msg, MAX_SIZE, 0);
if(byte_num > 0)
{
if(byte_num > MAX_SIZE)
byte_num = MAX_SIZE;
recv_msg[byte_num] = '\0';
printf("%s\n", recv_msg);
}
else if(byte_num < 0)
printf("接受消息出錯!\n");
else
{
printf("服務器端退出!\n");
exit(0);
}
}
}
}
return 0;
}
Server.c
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include <sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#define BACKLOG 5 //完成三次握手但沒有accept的隊列的長度
#define MAX_CLIENT 8 //應用層同時可以處理的連接
#define MAX_SIZE 1024
#define QUIT_CMD ".quit"
static void usage(const char* proc)
{
printf("use help : %s [local_ip] [local_port]\n", proc);
}
int client_fds[MAX_CLIENT];
int main(int argc, const char * argv[])
{
if(argc != 3)
{
usage(argv[0]);
return -1;
}
char input_msg[MAX_SIZE];
char recv_msg[MAX_SIZE];
//本地地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
bzero(&(server_addr.sin_zero), 8);
//創建socket
int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock_fd == -1)
{
perror("socket error");
return 1;
}
//綁定socket
int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(bind_result == -1)
{
perror("bind error");
return 1;
}
//listen
if(listen(server_sock_fd, BACKLOG) == -1)
{
perror("listen error");
return 1;
}
//fd_set
fd_set server_fd_set;
int max_fd = -1;
struct timeval tv; //超時時間設置
while(1)
{
tv.tv_sec = 20;
tv.tv_usec = 0;
FD_ZERO(&server_fd_set);
FD_SET(STDIN_FILENO, &server_fd_set);
if(max_fd <STDIN_FILENO)
max_fd = STDIN_FILENO;
//服務器端socket
FD_SET(server_sock_fd, &server_fd_set);
if(max_fd < server_sock_fd)
max_fd = server_sock_fd;
//客戶端連接
for(int i =0; i < MAX_CLIENT; i++)
if(client_fds[i] != 0)
{
FD_SET(client_fds[i], &server_fd_set);
if(max_fd < client_fds[i])
max_fd = client_fds[i];
}
int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);
if(ret <= 0)
continue;
else
{
//ret 爲未狀態發生變化的文件描述符的個數
if(FD_ISSET(STDIN_FILENO, &server_fd_set))
{
printf("發送消息:\n");
bzero(input_msg, MAX_SIZE);
fgets(input_msg, MAX_SIZE, stdin);
//輸入".quit"則退出服務器
if(strcmp(input_msg, QUIT_CMD) == 0)
exit(0);
char input_msg_new[MAX_SIZE];
strcpy(input_msg_new,"服務器:");
strcat(input_msg_new,input_msg);
for(int i = 0; i < MAX_CLIENT; i++)
if(client_fds[i] != 0)
{
printf("client_fds[%d]=%d\n", i, client_fds[i]);
send(client_fds[i], input_msg_new, MAX_SIZE, 0);
}
}
if(FD_ISSET(server_sock_fd, &server_fd_set))
{
//有新的連接請求
struct sockaddr_in client_address;
socklen_t address_len;
int client_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);
printf("新的連接建立client_sock_fd = %d\n", client_sock_fd);
if(client_sock_fd > 0)
{
int index = -1;
for(int i = 0; i < MAX_CLIENT; i++)
if(client_fds[i] == 0)
{
index = i;
client_fds[i] = client_sock_fd;
break;
}
if(index >= 0)
printf("新客戶端(%d)加入成功\n", index);
else
{
bzero(input_msg, MAX_SIZE);
strcpy(input_msg, "服務器加入的客戶端數達到最大值,無法加入!\n");
send(client_sock_fd, input_msg, MAX_SIZE, 0);
printf("客戶端連接數達到最大值,新客戶端加入失敗 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
}
}
}
for(int i =0; i < MAX_CLIENT; i++)
if(client_fds[i] !=0)
{
if(FD_ISSET(client_fds[i], &server_fd_set))
{
//處理某個客戶端過來的消息
bzero(recv_msg, MAX_SIZE);
long byte_num = recv(client_fds[i], recv_msg, MAX_SIZE, 0);
if (byte_num > 0)
{
recv_msg[byte_num > MAX_SIZE? MAX_SIZE:byte_num] = '\0';
char dest[10] = {""};
char new_recv_msg[MAX_SIZE];
strncpy(dest, recv_msg, 4);
if(strcmp(dest, "send") == 0)
{
int now=recv_msg[4]-48;
strncpy(new_recv_msg, recv_msg+6, MAX_SIZE);
printf("客戶端(%d)向客戶端(%d)發送私聊消息:%s\n", i,now,new_recv_msg);
char message_pri[MAX_SIZE];
message_pri[0]='(';
message_pri[1]=i+48;
message_pri[2]=')';
message_pri[3]='\0';
char new_message[MAX_SIZE];
strcpy(new_message,"客戶端");
strcat(new_message, message_pri);
strcat(new_message, "給你私發一條消息:");
int status=0;
strcat(new_message,new_recv_msg);
for(int j = 0; j < MAX_CLIENT; j++)
if(client_fds[j] != 0&&j==now)
{
send(client_fds[j], new_message, MAX_SIZE, 0);
status++;
}
if(status==0)
send(client_fds[i], "你私聊的對象不存在\n", MAX_SIZE, 0);
}
else if(strcmp(dest, "show") == 0)
{
int all=0;
for(int i = 0; i < MAX_CLIENT; i++)
if(client_fds[i] != 0)
all++;
printf("客戶端(%d)查看當前在線人數\n",i);
char new_message[MAX_SIZE];
strcpy(new_message,"當前在線的客戶端數量爲");
char number_all[10];
number_all[0]=48+all;
number_all[1]='\0';
strcat(new_message, number_all);
strcat(new_message, "個,分別是:客戶端(");
for(int j = 0; j < MAX_CLIENT; j++)
if(client_fds[j] != 0)
{
number_all[0]=48+j;
strcat(new_message, number_all);
strcat(new_message, ",");
}
strcat(new_message, ")\n");
for(int k = 0; k < MAX_CLIENT; k++)
if(client_fds[k] != 0&&k==i)
send(client_fds[k], new_message, MAX_SIZE, 0);
}
else{
printf("客戶端(%d)的羣聊消息:%s\n", i, recv_msg);
char x=48+i;
char message[MAX_SIZE];
message[0]='(';
message[1]=x;
message[2]=')';
message[3]=':';
message[4]='\0';
char new_message[MAX_SIZE];
strcpy(new_message,"客戶端");
strcat(new_message, message);
strcat(new_message,recv_msg);
for(int j = 0; j < MAX_CLIENT; j++)
if(client_fds[j] != 0&&j!=i)
send(client_fds[j], new_message, MAX_SIZE, 0);
}
}
else if(byte_num < 0)
printf("從客戶端(%d)接受消息出錯.\n", i);
else
{
FD_CLR(client_fds[i], &server_fd_set);
client_fds[i] = 0;
printf("客戶端(%d)退出了\n", i);
}
}
}
}
}
return 0;
}
通過makefile編譯後啓動服務器,再啓動三個客戶端測試:
服務器給客戶端發送消息:
客戶端發羣聊消息:
客戶端發送show查看當前在線客戶端信息:
客戶端通過send+客戶端編號+消息,私聊:
還有些細節功能筆者就不一一舉例了,筆者還會繼續分享一些Linux的小實驗,感興趣的讀者可以區筆者主頁查看。