C 中客户端之间的 ChatRoom 服务 - TCP
ChatRoom Service between Clients in C - TCP
开始吧,我想做的是多客户端聊天服务。
我已经阅读了数千篇与之相关的帖子,但其中大部分都是用线程实现的,对我没有任何帮助,我需要使用 FORKS 来完成。
我的服务器支持多客户端连接。每次客户端请求连接时,服务器都会执行以下操作:
- 设置所需的共享变量。
- 获取正确的套接字来处理连接,
- 当客户端连接时,将客户端的数据保存在用结构体实现的客户端数组中,
- 派生一个进程来处理这个连接,
- 返回并阻塞在等待另一个客户端的 accept() 函数中。
分叉执行以下操作:
- 等待用户发送的命令,
- 完成请求,
- 等待另一个命令。
该分支与受信号量保护的共享客户端数组一起工作。这是(由父进程)使用 shm_open、mmap 和 shm_open 完成的,以便在子进程之间共享数组。
目前只有 3 个选项:
- clientlist : 查看连接的客户端列表,
- sendchat : 向所需的客户端发送消息,
- quit_ : 退出程序并断开与服务器的连接。
话虽如此,问题是我无法以任何方式通知客户消息已准备好发送给他。执行流程为:
- 客户端 C1 连接,客户端 C2 连接。
- C1 想向 C2 发送消息,C1 告诉他的进程他想与 C2 交谈。
- 处理C1连接的进程,在共享数组中查找C2的名称,然后将C1发送的消息写入C2的缓冲区。
- 这就是我卡住的地方..我不知道如何让 C2 通知这是给 im 的新消息。
我知道这对任何人来说都太长了,但如果可以的话,我很乐意得到一些帮助。
以下是客户端和服务器脚本。
注意: server.c 编译时使用-lptrhead 和-lrt 绑定共享内存库。
注意: 服务器从函数 get_tcp_listen 中正确获取套接字,如您所见,无需担心关于这个。
我该如何解决这个问题?谢谢!
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "socketlib.h" // FOR BINDINGS, GET SOCKET, AND STUFF
#define SERVER_PORT "50000"
#define CLIENT_PORT "50001"
#define MAXDATASIZE 256
#define MAXTIMESIZE 30
#define NICKSIZE 25
#define MAXCLIENT 10
#define MAXMSG 1024
typedef struct{
char nick[NICKSIZE]; // NICK
char ip[NI_MAXHOST]; // IP
char port[NI_MAXSERV]; // PORT
char connTime[MAXTIMESIZE]; // TIMESTAMP
int connected; // STATE
pid_t pidConn; // PROCESS PID
char *msg; // MSG BUFFER
}connData;
int main (int argc, char **argv) {
// GET SOCKET
int sockfd;
if ((sockfd = get_tcp_connect(argv[1], SERVER_PORT, CLIENT_PORT)) == -1) {
fprintf(stderr, "Error client: client_connect\n");
exit(1);}
///////////////////////////////////////////////////////////////////////
time_t ltime;
ltime = time(NULL);
char timeStamp[MAXTIMESIZE];
strcpy(timeStamp,ctime(<ime));
printf("\n%s\n", timeStamp);
// GET MY IP : PORT
char ip_num[NI_MAXHOST];
char port_num[NI_MAXSERV];
get_socket_addr(sockfd, ip_num, port_num);
get_peer_addr(sockfd, ip_num, port_num);
///////////////////////////////////////////////////////////////////////
// WELLCOME MSG FROM SERVER
char *well = (char*) malloc(MAXDATASIZE); int numbytes;
if ((numbytes = recv(sockfd, well, MAXDATASIZE-1, 0)) == -1) {
fprintf(stderr, "Error client: recv WELLCOME\n");
exit(1);
}
well[numbytes] = '[=10=]'; printf("%s\n", well); free(well);
//////////////////////////////////////////////////////////////////////
// SEND NICK TO SERVER
char nick[NICKSIZE];
printf("\nEnter your NickName (25 chars): "); scanf("%s",nick);
if(send(sockfd, nick, NICKSIZE, 0) == -1){
fprintf(stderr,"Error client: send NICK\n");
exit(1);
}
///////////////////////////////////////////////////////////////////////
// GET CONNECTED USERS LIST FROM SERVER
int cantClients = 0; // FIRST: QUANTITY OF USERS
if (recv(sockfd, &cantClients, sizeof(int), 0) == -1) {
fprintf(stderr, "Error client: recv CANT CLIENTs\n");
exit(1);
}
connData *tmpCl = (connData *) malloc(sizeof(connData)*MAXCLIENT);
if (recv(sockfd, tmpCl, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error client: recv ARRAY CLIENTS\n");
exit(1);
}
printf("\n****\tConnected Users\t****\n");
int i;
for(i = 0; i < cantClients; i++){
if(tmpCl[i].connected == 1){
printf("\nNick: %s\n", tmpCl[i].nick);
printf("IP: %s\n", tmpCl[i].ip);
printf("PORT: %s\n", tmpCl[i].port);
printf("Time: %s", tmpCl[i].connTime);
printf("Connected: %d\n", tmpCl[i].connected);
printf("PID: %d\n", tmpCl[i].pidConn);
printf("**********************************\n");
}
} free(tmpCl);
///////////////////////////////////////////////////////////////////////
// THE CLIENT PROCESS WAITS UNTIL THE USER TYPES A COMMAND
char *comm = (char*)malloc(MAXDATASIZE);
printf("\nEnter one option: ");
printf("\n\t-> clientlist TO SEE THE LIST OF CONNECTED CLIENTS\n");
printf("\t-> sendchat TO SEND A MESSAGE\n");
printf("\t-> quit_ TO QUIT CHAT\n>> ");
scanf("%s",comm);
int exitvar = 0;
while(exitvar == 0){
// PARA TRAER DATOS DEL SERVIDOR, ENVIO EL COMANDO, Y ME QUEDO ESPERANDO
if(send(sockfd, comm, MAXDATASIZE-1, 0) == -1){
fprintf(stderr,"Error client: send\n");
exit(1);
}
if(strcmp(comm,"clientlist") == 0){
// GET CONNECTED USERS LIST FROM SERVER
connData *tmpCl = (connData *) malloc(sizeof(connData)*MAXCLIENT);
if (recv(sockfd, tmpCl, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error client: recv ARRAY CLIENT\n");
exit(1);
}
printf("\n****\tConnected Users\t****\n"); int i;
cantClients = (unsigned) sizeof(*tmpCl) / (unsigned) sizeof(connData);
for(i = 0; i < MAXCLIENT; i++){
if(tmpCl[i].connected == 1){
printf("\nNick: %s\n", tmpCl[i].nick);
printf("IP: %s\n", tmpCl[i].ip);
printf("PORT: %s\n", tmpCl[i].port);
printf("Time: %s", tmpCl[i].connTime);
printf("Connected: %d\n", tmpCl[i].connected);
printf("PID: %d\n", tmpCl[i].pidConn);
printf("**********************************\n");
}
} free(tmpCl);
}else if(strcmp(comm,"sendchat") == 0){
printf("To whom you want to talk?... ");
char *chatNick = (char *) malloc(NICKSIZE);
fgets(chatNick, NICKSIZE, stdin);
fgets(chatNick, NICKSIZE, stdin);
if((strlen(chatNick)>0) && (chatNick[strlen(chatNick)-1] == '\n') ){
chatNick[strlen(chatNick)-1] = '[=10=]';
}
if(send(sockfd, chatNick, NICKSIZE, 0) == -1){
fprintf(stderr, "Error client: send CHAT NICK\n");
}
printf("Type your message...\n");
char *chat_msg = (char *) malloc(MAXMSG);
fgets(chat_msg,MAXMSG,stdin) ;
if((strlen(chat_msg)>0) && (chat_msg[strlen(chat_msg)-1] == '\n') ){
chat_msg[strlen(chat_msg)-1] = '[=10=]';
}
if(send(sockfd, chat_msg, MAXMSG, 0) == -1){
fprintf(stderr, "Error client: send CHAT\n");
}
free(chatNick);
free(chat_msg);
}else{
char *buf = (char*) malloc(MAXDATASIZE); int numbytes;
if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
fprintf(stderr, "Error client: recv\n");
exit(1);
}
buf[numbytes] = '[=10=]'; printf("-> %s\n", buf);
free(buf);
}
if(strcmp(comm, "quit_") != 0){
free(comm); comm = (char*)malloc(MAXDATASIZE);
printf("\nWhats next?... "); scanf("%s",comm);
}else{
close(sockfd);
exitvar = 1;
}
}
return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include "socketlib.h"
#include <semaphore.h>
#define SERVER_PORT "50000"
#define BACKLOG 10
#define MAXDATASIZE 256
#define NICKSIZE 25
#define MAXCLIENT 10
#define MAXTIMESIZE 30
#define MAXMSG 1024
// ESTRUCTURA QUE MANEJARA LA LISTA DE CLIENTES
typedef struct{
char nick[NICKSIZE]; // NICK
char ip[NI_MAXHOST]; // IP
char port[NI_MAXSERV]; // PORT
char connTime[MAXTIMESIZE]; // TIMESTAMP
int connected; // STATE
pid_t pidConn; // PROCESS PID
char *msg; // MSG BUFFER
}connData;
// NOT ZOMBIE PROCESSES
void sigchld_handler(int s) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
connData *client;
int *id;
int main (int argc, char **argv) {
// THE ARRAY OF CLIENTS IS SHARED BETWEEN THE PROCESSES
int smid = shm_open("shm1", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(smid, sizeof(connData)*MAXCLIENT);
// JUST FOR MAXCLIENT 10 CLIENTS AT THE MOMMENT
client = mmap(NULL, sizeof(connData)*MAXCLIENT, PROT_READ | PROT_WRITE, MAP_SHARED, smid, 0);
sem_t *sem; sem = sem_open("sem1", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
// THE ARRAY INDEX IS ALSO SHARED
int smid2 = shm_open("shm2", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(smid2, sizeof(int));
id = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, smid2, 0);
sem_t *sem2; sem2 = sem_open("sem2", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
sem_wait(sem2);
*id = 0;
sem_post(sem2);
// CONN CONFIG
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
fprintf(stderr, "Error server: sigaction\n");
exit(1);
}
int sockfd; // LISTENER
if ((sockfd = get_tcp_listen(SERVER_PORT, BACKLOG)) == -1) {
fprintf(stderr, "Error get_tcp_listen\n");
exit(1);
}
printf("server: waiting for connections...\n");
char ip_num[NI_MAXHOST];
char port_num[NI_MAXSERV];
get_socket_addr(sockfd, ip_num, port_num);
//////////////////////////////////////////////////////////////////
while (1) {
// BLOCKS UNTIL SOMEONE REQUEST CONN
int new_fd;
if ((new_fd = accept(sockfd, NULL, NULL)) == -1) {
fprintf(stderr, "Error server: accept\n");
continue;}
////////////////////////////////////////////////////////
// IP:PORT OF JUST CONNECTED USER
get_socket_addr(new_fd, ip_num, port_num);
get_peer_addr(new_fd, ip_num, port_num);
printf("server: got connection from: %s, %s\n", ip_num, port_num);
////////////////////////////////////////////////////////
// TIMESTAMP OF USER CONN
time_t ltime; ltime = time(NULL);
char timeStamp[MAXTIMESIZE]; strcpy(timeStamp,ctime(<ime));
////////////////////////////////////////////////////////////////////////
// WELLCOME MESSAGE SENT TO THE CLIENT
char *well = (char*) malloc(MAXDATASIZE);
if (send(new_fd, "Wellcome to the Chat Service!!\n", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error sending WELLCOME\n");
} free(well);
///////////////////////////////////////////////////////////////
// SAVES IN THE ARRAY OF CLIENTS, THE DATA OF THE CLIENT THAT JUST CONNECTED
int idTmp1;
sem_wait(sem2);
idTmp1 = *id;
sem_post(sem2);
if(sem_wait(sem) == 0){
strcpy(client[idTmp1].ip, ip_num); // IP
strcpy(client[idTmp1].port, port_num); // PORT
strcpy(client[idTmp1].connTime, timeStamp); // TIMESTAMP
client[idTmp1].connected = 1;
}else{
fprintf(stderr, "Error SEM_WAIT\n");
}
sem_post(sem);
sem_wait(sem2); (*id)++; sem_post(sem2);
//////////////////////////////////////////////////////////////
// FORKS A PROCESS TO DEAL WITH THE JUST CONNECTED USER
if (fork() == 0) {
close(sockfd); // CLOSES THE FATHERS SOCKET
int numbytes = 0;
// SAVES THE NICK IN THE ARRAY
char userNick[NICKSIZE];
if(( numbytes = recv(new_fd, userNick, NICKSIZE, 0)) == -1){
fprintf(stderr,"Error rcv\n");
} userNick[numbytes-1] = '[=11=]';
int idTmp2;
sem_wait(sem2);
pid_t pidAct = getpid(); // PID OF THE NEW CREATED FORK
idTmp2 = *id; // ID OF THE USER
idTmp2--;
strcpy(client[idTmp2].nick,userNick);
client[idTmp2].pidConn = pidAct;
idTmp2 = *id;
sem_post(sem2);
//////////////////////////////////////////////////////////////
// SENDS THE LIST OF CONNECTED CLIENTES
if (send(new_fd, id, sizeof(int), 0) == -1) {
fprintf(stderr, "Error send ID\n");
}
if (send(new_fd, client, sizeof(connData)*MAXCLIENT, 0) == -1) { // SEND THE WHOLE LIST
fprintf(stderr, "Error send LIST\n");
}
//////////////////////////////////////////////////////////////
// THE FORK WAITS SOME COMMAND OF THE USER
char *comm = (char*)malloc(MAXDATASIZE);
if( (numbytes = recv(new_fd, comm, MAXDATASIZE-1, 0)) == -1){
fprintf(stderr,"Error rcv COMMAND\n");
}
comm[numbytes] = '[=11=]';
// THE FORK ENTERS IN A LOOP WAITING COMMANDS
int wait = 0;
while(wait == 0){
if(strcmp(comm,"clientlist") == 0){
if (send(new_fd, client, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error send CLIENT LIST\n");
}
}else if(strcmp(comm,"sendchat") == 0){
char *chatNick = (char *) malloc(NICKSIZE); // WAIT FOR THE CLIENT TO TALK TO
if( (numbytes = recv(new_fd,chatNick, NICKSIZE, 0)) == -1){
fprintf(stderr,"Error server rcv CHAT NICK\n");
} chatNick[numbytes-1] = '[=11=]';
char *chatmsg = (char *)malloc(MAXMSG); // WAIT FOR MSG
if((numbytes = recv(new_fd, chatmsg, MAXMSG, 0)) == -1){
fprintf(stderr,"Error server rcv CHAT\n");
} chatmsg[numbytes-1] = '[=11=]';
int client_id;
sem_wait(sem2);
for(client_id = 0; client_id < *id; client_id++){
if(strcmp(client[client_id].nick, chatNick) == 0){
if(client[client_id].msg != NULL){
free(client[client_id].msg);
}
client[client_id].msg = (char * )malloc(MAXMSG); // COPY THE MESSAGE TO THE DESIRED USER
strcpy(client[client_id].msg, chatmsg);
printf("\nTHE MESSAGE TO: %s IS %s\n", client[client_id].nick, client[client_id].msg);
}
}
sem_post(sem2);
/*
HERE I HAVE THE NICK, SAY, 'client1' OF THE CLIENT TO WHICH I WANT TO TALK.
THE MSG NOW ITS IN HIS MSG BUFFER LIKE ABOVE.
HOW CAN I NOTICE THE FORKED PROCESS HANDLING THE CONNECTION of 'client1'
TO READ THE MESSAGE ?
*/
free(chatmsg);
free(chatNick);
}else if(strcmp(comm,"quit_") == 0){
if (send(new_fd, "Byee!!", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error send EXIT\n");
}
wait = 1; // FOR EXIT AND CLOSE THE SOCKET
}else{
if (send(new_fd, "Invalid option!", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error send INVALID\n");
}
}
if(wait == 0){
// WHEN THE FORKED PROCESS HAS FULFILL THE USERS REQUEST, IT JUST WAITS FOR OTHER REQUEST
free(comm); comm = (char*)malloc(MAXDATASIZE);
if((numbytes = recv(new_fd, comm, MAXDATASIZE-1, 0)) == -1){
fprintf(stderr,"Error rcv REQUEST\n");
} comm[numbytes] = '[=11=]';
}
}
if(munmap(client,sizeof(connData)*MAXCLIENT) != 0){ printf("ERROR FREEING MEM\n");}
sem_unlink("sem1"); shm_unlink("shm1");
printf("Connection ended with %d \n", new_fd);
close(new_fd); exit(0);
}
printf("Keep waiting connections.....\n");
close(new_fd); // SOCKET DEL ACCEPT, DEL CLIENTE QUE SE HABIA CONECTADO
//////////////////////////////////////////////////////////////////////////////
}
if(munmap(client,sizeof(connData)*MAXCLIENT) != 0){ printf("ERROR FREEING MEM\n");}
sem_unlink("sem1");
shm_unlink("shm1");
return 0;
}
首先我要指出 fork() 和共享内存并不是创建聊天服务器的最佳或最简单的方法;如果您可以选择,我建议您以不同的方式进行操作(例如,通过单个进程中的多个线程,甚至是单个线程和 select(),而不是)。
假设您需要使用这种方法(例如,因为它是在 class 作业中规定的,或者其他),但是......您缺少的是 IPC 通知机制,即(如你说)进程 C2 注意到进程 C1 有一些数据供 C2 处理的方法。
有几种方法可以实现通知...快速而简单的方法(可能足以完成 class 分配)只是让每个进程轮询共享内存区;例如让每个进程每 100 毫秒检查一次共享内存区域的一部分,看看自上次以来是否有任何变化。在这种情况下,C1 可能会向共享内存区域写入一些新数据,然后在完成写入后在共享内存区域中增加一个整数值。下次 C2 醒来检查整数时,它会注意到整数的值与上次检查时的值不同,并认为共享内存区域中有新数据可供它处理。 (旁注:当使用共享内存时,您应该以某种方式序列化对共享内存区域的访问,否则您可能会遇到竞争条件,例如 C2 在 C1 仍在写入内存的同时开始读取内存。其症状是系统99.999% 的时间都可以正常工作,但偶尔会在时间为 "just right")
时做一些事情 weird/wrong
但是,如果您想要一种不那么骇人听闻的通知方法(即不吃 CPU 24/7 循环并且不会在每次通过服务器的行程中造成不必要的 100 毫秒延迟的方法) ,那么您需要选择一种通知机制。一种机制是使用(可以说是错误命名的)kill() 系统调用向 C2 的进程发送 UNIX 信号;然后,C2 将需要安装一个信号处理程序,使其在收到该类型的信号时执行正确的操作。
但是,如果你想避免 Unix 信号(我尽量避免它们,因为它们相当陈旧和丑陋),你将需要一种更温和的机制,通过该机制可以在以下任一情况下唤醒 C2 进程(一个 IPC收到通知)或(I/O 收到数据),以先发生的情况为准...天真的阻塞-I/O-调用机制是不够的,因为它只允许您的进程等待一件事或其他,但不是两者。获得两者的一个好方法是使用 select() 或 poll() 同时监视两个套接字:一个套接字是到客户端的 TCP 连接,另一个套接字是进程已设置的 UDP 套接字专门用于接收 IPC 通知。每个分叉进程都设置一个 UDP 套接字侦听特定端口,当 C1 想要唤醒 C2 时,C1 通过将 UDP 数据包发送到 C2 的 UDP 端口来实现。 (UDP 数据包的内容无关紧要,因为它的唯一目的是使 C2 从 select() 变为 return,并且由于 UDP 套接字已 select 准备就绪- for-read,C2 然后知道从 UDP 套接字读取数据包,丢弃数据包,然后检查共享内存区域是否有新数据。
(当然,一旦你完成了所有这些,你可能会发现 C1 更容易将 C2 的数据简单地包含在 UDP 数据包本身中,这样 C2 就不必处理潜在的共享内存地区,但这取决于您)
你快完成@Emiliano,这就是我也卡住一次的要点;)。
好吧,你有一些选项可以告诉其他进程有关消息的信息。
1.你总是寻找自己的消息缓冲区(这很糟糕,消耗很多CPU也是一个坏主意)
进程 C1 总是在 C1 的共享内存中查找它自己的内存,缓冲并检查是否有新消息,然后发送给客户端。
2。您使用信号(优于 1)
一个。客户端 C2 为 C1 发送消息。
b。进程 C2 将消息存储在共享内存上的 C1 缓冲区中。(如果我正确理解您的共享内存结构)
c。进程 C2 向 C1 发送一个信号,通知我在你的缓冲区中为你放置了一条消息。(在这种情况下,你需要知道哪个 pid 处理哪个客户端)
d。从进程获取信号后,C1 检查其缓冲区并发送到其客户端。
编辑:
看来你的信号有问题。
这是一个显示信号 sending/catching.
的简单片段
recvsig.c
static void handler(int sig, siginfo_t *si, void *data)
{
printf("%s = %d\n", "Got a signal, signal number is ", sig);
//you can also code here what you want, after getting a signal
}
void init_signal()
{
struct sigaction act;
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN + 1, &act, NULL);
}
void main(int argc, char **argv)
{
printf("%s %d\n", "PID", getpid());
init_signal();
while(1)
{
pause();
{
printf("%s\n", "Received a signal");
//code here anything after you got signal
}
}
return;
}
sendsig.c
void main(int argc, char **argv)
{
int pid = atoi(argv[1]);
while(1)
{
sleep(5);
{
kill(pid, SIGRTMIN+1);
printf("%s %d\n", "Sent a signal to ", pid);
}
}
return;
}
在您的程序中,在每个分叉子进程后调用 init_signal()
。
确保您管理所有分叉进程 + 连接的客户端的 pid 列表。
并使用 kill()
发出正确的 pid。
开始吧,我想做的是多客户端聊天服务。 我已经阅读了数千篇与之相关的帖子,但其中大部分都是用线程实现的,对我没有任何帮助,我需要使用 FORKS 来完成。
我的服务器支持多客户端连接。每次客户端请求连接时,服务器都会执行以下操作:
- 设置所需的共享变量。
- 获取正确的套接字来处理连接,
- 当客户端连接时,将客户端的数据保存在用结构体实现的客户端数组中,
- 派生一个进程来处理这个连接,
- 返回并阻塞在等待另一个客户端的 accept() 函数中。
分叉执行以下操作:
- 等待用户发送的命令,
- 完成请求,
- 等待另一个命令。 该分支与受信号量保护的共享客户端数组一起工作。这是(由父进程)使用 shm_open、mmap 和 shm_open 完成的,以便在子进程之间共享数组。
目前只有 3 个选项:
- clientlist : 查看连接的客户端列表,
- sendchat : 向所需的客户端发送消息,
- quit_ : 退出程序并断开与服务器的连接。
话虽如此,问题是我无法以任何方式通知客户消息已准备好发送给他。执行流程为:
- 客户端 C1 连接,客户端 C2 连接。
- C1 想向 C2 发送消息,C1 告诉他的进程他想与 C2 交谈。
- 处理C1连接的进程,在共享数组中查找C2的名称,然后将C1发送的消息写入C2的缓冲区。
- 这就是我卡住的地方..我不知道如何让 C2 通知这是给 im 的新消息。
我知道这对任何人来说都太长了,但如果可以的话,我很乐意得到一些帮助。
以下是客户端和服务器脚本。
注意: server.c 编译时使用-lptrhead 和-lrt 绑定共享内存库。
注意: 服务器从函数 get_tcp_listen 中正确获取套接字,如您所见,无需担心关于这个。
我该如何解决这个问题?谢谢!
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "socketlib.h" // FOR BINDINGS, GET SOCKET, AND STUFF
#define SERVER_PORT "50000"
#define CLIENT_PORT "50001"
#define MAXDATASIZE 256
#define MAXTIMESIZE 30
#define NICKSIZE 25
#define MAXCLIENT 10
#define MAXMSG 1024
typedef struct{
char nick[NICKSIZE]; // NICK
char ip[NI_MAXHOST]; // IP
char port[NI_MAXSERV]; // PORT
char connTime[MAXTIMESIZE]; // TIMESTAMP
int connected; // STATE
pid_t pidConn; // PROCESS PID
char *msg; // MSG BUFFER
}connData;
int main (int argc, char **argv) {
// GET SOCKET
int sockfd;
if ((sockfd = get_tcp_connect(argv[1], SERVER_PORT, CLIENT_PORT)) == -1) {
fprintf(stderr, "Error client: client_connect\n");
exit(1);}
///////////////////////////////////////////////////////////////////////
time_t ltime;
ltime = time(NULL);
char timeStamp[MAXTIMESIZE];
strcpy(timeStamp,ctime(<ime));
printf("\n%s\n", timeStamp);
// GET MY IP : PORT
char ip_num[NI_MAXHOST];
char port_num[NI_MAXSERV];
get_socket_addr(sockfd, ip_num, port_num);
get_peer_addr(sockfd, ip_num, port_num);
///////////////////////////////////////////////////////////////////////
// WELLCOME MSG FROM SERVER
char *well = (char*) malloc(MAXDATASIZE); int numbytes;
if ((numbytes = recv(sockfd, well, MAXDATASIZE-1, 0)) == -1) {
fprintf(stderr, "Error client: recv WELLCOME\n");
exit(1);
}
well[numbytes] = '[=10=]'; printf("%s\n", well); free(well);
//////////////////////////////////////////////////////////////////////
// SEND NICK TO SERVER
char nick[NICKSIZE];
printf("\nEnter your NickName (25 chars): "); scanf("%s",nick);
if(send(sockfd, nick, NICKSIZE, 0) == -1){
fprintf(stderr,"Error client: send NICK\n");
exit(1);
}
///////////////////////////////////////////////////////////////////////
// GET CONNECTED USERS LIST FROM SERVER
int cantClients = 0; // FIRST: QUANTITY OF USERS
if (recv(sockfd, &cantClients, sizeof(int), 0) == -1) {
fprintf(stderr, "Error client: recv CANT CLIENTs\n");
exit(1);
}
connData *tmpCl = (connData *) malloc(sizeof(connData)*MAXCLIENT);
if (recv(sockfd, tmpCl, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error client: recv ARRAY CLIENTS\n");
exit(1);
}
printf("\n****\tConnected Users\t****\n");
int i;
for(i = 0; i < cantClients; i++){
if(tmpCl[i].connected == 1){
printf("\nNick: %s\n", tmpCl[i].nick);
printf("IP: %s\n", tmpCl[i].ip);
printf("PORT: %s\n", tmpCl[i].port);
printf("Time: %s", tmpCl[i].connTime);
printf("Connected: %d\n", tmpCl[i].connected);
printf("PID: %d\n", tmpCl[i].pidConn);
printf("**********************************\n");
}
} free(tmpCl);
///////////////////////////////////////////////////////////////////////
// THE CLIENT PROCESS WAITS UNTIL THE USER TYPES A COMMAND
char *comm = (char*)malloc(MAXDATASIZE);
printf("\nEnter one option: ");
printf("\n\t-> clientlist TO SEE THE LIST OF CONNECTED CLIENTS\n");
printf("\t-> sendchat TO SEND A MESSAGE\n");
printf("\t-> quit_ TO QUIT CHAT\n>> ");
scanf("%s",comm);
int exitvar = 0;
while(exitvar == 0){
// PARA TRAER DATOS DEL SERVIDOR, ENVIO EL COMANDO, Y ME QUEDO ESPERANDO
if(send(sockfd, comm, MAXDATASIZE-1, 0) == -1){
fprintf(stderr,"Error client: send\n");
exit(1);
}
if(strcmp(comm,"clientlist") == 0){
// GET CONNECTED USERS LIST FROM SERVER
connData *tmpCl = (connData *) malloc(sizeof(connData)*MAXCLIENT);
if (recv(sockfd, tmpCl, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error client: recv ARRAY CLIENT\n");
exit(1);
}
printf("\n****\tConnected Users\t****\n"); int i;
cantClients = (unsigned) sizeof(*tmpCl) / (unsigned) sizeof(connData);
for(i = 0; i < MAXCLIENT; i++){
if(tmpCl[i].connected == 1){
printf("\nNick: %s\n", tmpCl[i].nick);
printf("IP: %s\n", tmpCl[i].ip);
printf("PORT: %s\n", tmpCl[i].port);
printf("Time: %s", tmpCl[i].connTime);
printf("Connected: %d\n", tmpCl[i].connected);
printf("PID: %d\n", tmpCl[i].pidConn);
printf("**********************************\n");
}
} free(tmpCl);
}else if(strcmp(comm,"sendchat") == 0){
printf("To whom you want to talk?... ");
char *chatNick = (char *) malloc(NICKSIZE);
fgets(chatNick, NICKSIZE, stdin);
fgets(chatNick, NICKSIZE, stdin);
if((strlen(chatNick)>0) && (chatNick[strlen(chatNick)-1] == '\n') ){
chatNick[strlen(chatNick)-1] = '[=10=]';
}
if(send(sockfd, chatNick, NICKSIZE, 0) == -1){
fprintf(stderr, "Error client: send CHAT NICK\n");
}
printf("Type your message...\n");
char *chat_msg = (char *) malloc(MAXMSG);
fgets(chat_msg,MAXMSG,stdin) ;
if((strlen(chat_msg)>0) && (chat_msg[strlen(chat_msg)-1] == '\n') ){
chat_msg[strlen(chat_msg)-1] = '[=10=]';
}
if(send(sockfd, chat_msg, MAXMSG, 0) == -1){
fprintf(stderr, "Error client: send CHAT\n");
}
free(chatNick);
free(chat_msg);
}else{
char *buf = (char*) malloc(MAXDATASIZE); int numbytes;
if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
fprintf(stderr, "Error client: recv\n");
exit(1);
}
buf[numbytes] = '[=10=]'; printf("-> %s\n", buf);
free(buf);
}
if(strcmp(comm, "quit_") != 0){
free(comm); comm = (char*)malloc(MAXDATASIZE);
printf("\nWhats next?... "); scanf("%s",comm);
}else{
close(sockfd);
exitvar = 1;
}
}
return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include "socketlib.h"
#include <semaphore.h>
#define SERVER_PORT "50000"
#define BACKLOG 10
#define MAXDATASIZE 256
#define NICKSIZE 25
#define MAXCLIENT 10
#define MAXTIMESIZE 30
#define MAXMSG 1024
// ESTRUCTURA QUE MANEJARA LA LISTA DE CLIENTES
typedef struct{
char nick[NICKSIZE]; // NICK
char ip[NI_MAXHOST]; // IP
char port[NI_MAXSERV]; // PORT
char connTime[MAXTIMESIZE]; // TIMESTAMP
int connected; // STATE
pid_t pidConn; // PROCESS PID
char *msg; // MSG BUFFER
}connData;
// NOT ZOMBIE PROCESSES
void sigchld_handler(int s) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
connData *client;
int *id;
int main (int argc, char **argv) {
// THE ARRAY OF CLIENTS IS SHARED BETWEEN THE PROCESSES
int smid = shm_open("shm1", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(smid, sizeof(connData)*MAXCLIENT);
// JUST FOR MAXCLIENT 10 CLIENTS AT THE MOMMENT
client = mmap(NULL, sizeof(connData)*MAXCLIENT, PROT_READ | PROT_WRITE, MAP_SHARED, smid, 0);
sem_t *sem; sem = sem_open("sem1", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
// THE ARRAY INDEX IS ALSO SHARED
int smid2 = shm_open("shm2", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(smid2, sizeof(int));
id = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, smid2, 0);
sem_t *sem2; sem2 = sem_open("sem2", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
sem_wait(sem2);
*id = 0;
sem_post(sem2);
// CONN CONFIG
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
fprintf(stderr, "Error server: sigaction\n");
exit(1);
}
int sockfd; // LISTENER
if ((sockfd = get_tcp_listen(SERVER_PORT, BACKLOG)) == -1) {
fprintf(stderr, "Error get_tcp_listen\n");
exit(1);
}
printf("server: waiting for connections...\n");
char ip_num[NI_MAXHOST];
char port_num[NI_MAXSERV];
get_socket_addr(sockfd, ip_num, port_num);
//////////////////////////////////////////////////////////////////
while (1) {
// BLOCKS UNTIL SOMEONE REQUEST CONN
int new_fd;
if ((new_fd = accept(sockfd, NULL, NULL)) == -1) {
fprintf(stderr, "Error server: accept\n");
continue;}
////////////////////////////////////////////////////////
// IP:PORT OF JUST CONNECTED USER
get_socket_addr(new_fd, ip_num, port_num);
get_peer_addr(new_fd, ip_num, port_num);
printf("server: got connection from: %s, %s\n", ip_num, port_num);
////////////////////////////////////////////////////////
// TIMESTAMP OF USER CONN
time_t ltime; ltime = time(NULL);
char timeStamp[MAXTIMESIZE]; strcpy(timeStamp,ctime(<ime));
////////////////////////////////////////////////////////////////////////
// WELLCOME MESSAGE SENT TO THE CLIENT
char *well = (char*) malloc(MAXDATASIZE);
if (send(new_fd, "Wellcome to the Chat Service!!\n", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error sending WELLCOME\n");
} free(well);
///////////////////////////////////////////////////////////////
// SAVES IN THE ARRAY OF CLIENTS, THE DATA OF THE CLIENT THAT JUST CONNECTED
int idTmp1;
sem_wait(sem2);
idTmp1 = *id;
sem_post(sem2);
if(sem_wait(sem) == 0){
strcpy(client[idTmp1].ip, ip_num); // IP
strcpy(client[idTmp1].port, port_num); // PORT
strcpy(client[idTmp1].connTime, timeStamp); // TIMESTAMP
client[idTmp1].connected = 1;
}else{
fprintf(stderr, "Error SEM_WAIT\n");
}
sem_post(sem);
sem_wait(sem2); (*id)++; sem_post(sem2);
//////////////////////////////////////////////////////////////
// FORKS A PROCESS TO DEAL WITH THE JUST CONNECTED USER
if (fork() == 0) {
close(sockfd); // CLOSES THE FATHERS SOCKET
int numbytes = 0;
// SAVES THE NICK IN THE ARRAY
char userNick[NICKSIZE];
if(( numbytes = recv(new_fd, userNick, NICKSIZE, 0)) == -1){
fprintf(stderr,"Error rcv\n");
} userNick[numbytes-1] = '[=11=]';
int idTmp2;
sem_wait(sem2);
pid_t pidAct = getpid(); // PID OF THE NEW CREATED FORK
idTmp2 = *id; // ID OF THE USER
idTmp2--;
strcpy(client[idTmp2].nick,userNick);
client[idTmp2].pidConn = pidAct;
idTmp2 = *id;
sem_post(sem2);
//////////////////////////////////////////////////////////////
// SENDS THE LIST OF CONNECTED CLIENTES
if (send(new_fd, id, sizeof(int), 0) == -1) {
fprintf(stderr, "Error send ID\n");
}
if (send(new_fd, client, sizeof(connData)*MAXCLIENT, 0) == -1) { // SEND THE WHOLE LIST
fprintf(stderr, "Error send LIST\n");
}
//////////////////////////////////////////////////////////////
// THE FORK WAITS SOME COMMAND OF THE USER
char *comm = (char*)malloc(MAXDATASIZE);
if( (numbytes = recv(new_fd, comm, MAXDATASIZE-1, 0)) == -1){
fprintf(stderr,"Error rcv COMMAND\n");
}
comm[numbytes] = '[=11=]';
// THE FORK ENTERS IN A LOOP WAITING COMMANDS
int wait = 0;
while(wait == 0){
if(strcmp(comm,"clientlist") == 0){
if (send(new_fd, client, sizeof(connData)*MAXCLIENT, 0) == -1) {
fprintf(stderr, "Error send CLIENT LIST\n");
}
}else if(strcmp(comm,"sendchat") == 0){
char *chatNick = (char *) malloc(NICKSIZE); // WAIT FOR THE CLIENT TO TALK TO
if( (numbytes = recv(new_fd,chatNick, NICKSIZE, 0)) == -1){
fprintf(stderr,"Error server rcv CHAT NICK\n");
} chatNick[numbytes-1] = '[=11=]';
char *chatmsg = (char *)malloc(MAXMSG); // WAIT FOR MSG
if((numbytes = recv(new_fd, chatmsg, MAXMSG, 0)) == -1){
fprintf(stderr,"Error server rcv CHAT\n");
} chatmsg[numbytes-1] = '[=11=]';
int client_id;
sem_wait(sem2);
for(client_id = 0; client_id < *id; client_id++){
if(strcmp(client[client_id].nick, chatNick) == 0){
if(client[client_id].msg != NULL){
free(client[client_id].msg);
}
client[client_id].msg = (char * )malloc(MAXMSG); // COPY THE MESSAGE TO THE DESIRED USER
strcpy(client[client_id].msg, chatmsg);
printf("\nTHE MESSAGE TO: %s IS %s\n", client[client_id].nick, client[client_id].msg);
}
}
sem_post(sem2);
/*
HERE I HAVE THE NICK, SAY, 'client1' OF THE CLIENT TO WHICH I WANT TO TALK.
THE MSG NOW ITS IN HIS MSG BUFFER LIKE ABOVE.
HOW CAN I NOTICE THE FORKED PROCESS HANDLING THE CONNECTION of 'client1'
TO READ THE MESSAGE ?
*/
free(chatmsg);
free(chatNick);
}else if(strcmp(comm,"quit_") == 0){
if (send(new_fd, "Byee!!", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error send EXIT\n");
}
wait = 1; // FOR EXIT AND CLOSE THE SOCKET
}else{
if (send(new_fd, "Invalid option!", MAXDATASIZE-1, 0) == -1) {
fprintf(stderr, "Error send INVALID\n");
}
}
if(wait == 0){
// WHEN THE FORKED PROCESS HAS FULFILL THE USERS REQUEST, IT JUST WAITS FOR OTHER REQUEST
free(comm); comm = (char*)malloc(MAXDATASIZE);
if((numbytes = recv(new_fd, comm, MAXDATASIZE-1, 0)) == -1){
fprintf(stderr,"Error rcv REQUEST\n");
} comm[numbytes] = '[=11=]';
}
}
if(munmap(client,sizeof(connData)*MAXCLIENT) != 0){ printf("ERROR FREEING MEM\n");}
sem_unlink("sem1"); shm_unlink("shm1");
printf("Connection ended with %d \n", new_fd);
close(new_fd); exit(0);
}
printf("Keep waiting connections.....\n");
close(new_fd); // SOCKET DEL ACCEPT, DEL CLIENTE QUE SE HABIA CONECTADO
//////////////////////////////////////////////////////////////////////////////
}
if(munmap(client,sizeof(connData)*MAXCLIENT) != 0){ printf("ERROR FREEING MEM\n");}
sem_unlink("sem1");
shm_unlink("shm1");
return 0;
}
首先我要指出 fork() 和共享内存并不是创建聊天服务器的最佳或最简单的方法;如果您可以选择,我建议您以不同的方式进行操作(例如,通过单个进程中的多个线程,甚至是单个线程和 select(),而不是)。
假设您需要使用这种方法(例如,因为它是在 class 作业中规定的,或者其他),但是......您缺少的是 IPC 通知机制,即(如你说)进程 C2 注意到进程 C1 有一些数据供 C2 处理的方法。
有几种方法可以实现通知...快速而简单的方法(可能足以完成 class 分配)只是让每个进程轮询共享内存区;例如让每个进程每 100 毫秒检查一次共享内存区域的一部分,看看自上次以来是否有任何变化。在这种情况下,C1 可能会向共享内存区域写入一些新数据,然后在完成写入后在共享内存区域中增加一个整数值。下次 C2 醒来检查整数时,它会注意到整数的值与上次检查时的值不同,并认为共享内存区域中有新数据可供它处理。 (旁注:当使用共享内存时,您应该以某种方式序列化对共享内存区域的访问,否则您可能会遇到竞争条件,例如 C2 在 C1 仍在写入内存的同时开始读取内存。其症状是系统99.999% 的时间都可以正常工作,但偶尔会在时间为 "just right")
时做一些事情 weird/wrong但是,如果您想要一种不那么骇人听闻的通知方法(即不吃 CPU 24/7 循环并且不会在每次通过服务器的行程中造成不必要的 100 毫秒延迟的方法) ,那么您需要选择一种通知机制。一种机制是使用(可以说是错误命名的)kill() 系统调用向 C2 的进程发送 UNIX 信号;然后,C2 将需要安装一个信号处理程序,使其在收到该类型的信号时执行正确的操作。
但是,如果你想避免 Unix 信号(我尽量避免它们,因为它们相当陈旧和丑陋),你将需要一种更温和的机制,通过该机制可以在以下任一情况下唤醒 C2 进程(一个 IPC收到通知)或(I/O 收到数据),以先发生的情况为准...天真的阻塞-I/O-调用机制是不够的,因为它只允许您的进程等待一件事或其他,但不是两者。获得两者的一个好方法是使用 select() 或 poll() 同时监视两个套接字:一个套接字是到客户端的 TCP 连接,另一个套接字是进程已设置的 UDP 套接字专门用于接收 IPC 通知。每个分叉进程都设置一个 UDP 套接字侦听特定端口,当 C1 想要唤醒 C2 时,C1 通过将 UDP 数据包发送到 C2 的 UDP 端口来实现。 (UDP 数据包的内容无关紧要,因为它的唯一目的是使 C2 从 select() 变为 return,并且由于 UDP 套接字已 select 准备就绪- for-read,C2 然后知道从 UDP 套接字读取数据包,丢弃数据包,然后检查共享内存区域是否有新数据。
(当然,一旦你完成了所有这些,你可能会发现 C1 更容易将 C2 的数据简单地包含在 UDP 数据包本身中,这样 C2 就不必处理潜在的共享内存地区,但这取决于您)
你快完成@Emiliano,这就是我也卡住一次的要点;)。
好吧,你有一些选项可以告诉其他进程有关消息的信息。
1.你总是寻找自己的消息缓冲区(这很糟糕,消耗很多CPU也是一个坏主意)
进程 C1 总是在 C1 的共享内存中查找它自己的内存,缓冲并检查是否有新消息,然后发送给客户端。
2。您使用信号(优于 1)
一个。客户端 C2 为 C1 发送消息。
b。进程 C2 将消息存储在共享内存上的 C1 缓冲区中。(如果我正确理解您的共享内存结构)
c。进程 C2 向 C1 发送一个信号,通知我在你的缓冲区中为你放置了一条消息。(在这种情况下,你需要知道哪个 pid 处理哪个客户端)
d。从进程获取信号后,C1 检查其缓冲区并发送到其客户端。
编辑: 看来你的信号有问题。 这是一个显示信号 sending/catching.
的简单片段recvsig.c
static void handler(int sig, siginfo_t *si, void *data)
{
printf("%s = %d\n", "Got a signal, signal number is ", sig);
//you can also code here what you want, after getting a signal
}
void init_signal()
{
struct sigaction act;
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN + 1, &act, NULL);
}
void main(int argc, char **argv)
{
printf("%s %d\n", "PID", getpid());
init_signal();
while(1)
{
pause();
{
printf("%s\n", "Received a signal");
//code here anything after you got signal
}
}
return;
}
sendsig.c
void main(int argc, char **argv)
{
int pid = atoi(argv[1]);
while(1)
{
sleep(5);
{
kill(pid, SIGRTMIN+1);
printf("%s %d\n", "Sent a signal to ", pid);
}
}
return;
}
在您的程序中,在每个分叉子进程后调用 init_signal()
。
确保您管理所有分叉进程 + 连接的客户端的 pid 列表。
并使用 kill()
发出正确的 pid。