综合练习:局域网聊天室

alternate text
// 单向线性链表
//
#ifndef _LIST_H
#define _LIST_H

#include <sys/types.h>
#include <stdbool.h>

// 节点
typedef struct ListNode {
	void*            data; // 数据
	struct ListNode* next; // 后指针
}	LIST_NODE;

// 链表
typedef struct List {
	LIST_NODE* head; // 头指针
	LIST_NODE* tail; // 尾指针
	LIST_NODE* iter; // 迭代器
}	LIST;

// 创建
LIST* list_create (void);

// 销毁
void list_destroy (LIST* list);

// 压入
void list_push (LIST* list, void* data);

// 弹出
void* list_pop (LIST* list);

// 删除
void list_remove (LIST* list, void* data);

// 清空
void list_clear (LIST* list);

// 判空
bool list_empty (LIST* list);

// 开始
void list_begin (LIST* list);

// 继续
void** list_next (LIST* list);

// 终止
bool list_end (LIST* list);

#endif // _LIST_H
#ifndef _CHATROOM_H
#define _CHATROOM_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "list.h"

#define MAX_NCK  256                // 最大昵称
#define MAX_TAG (4+MAX_NCK)         // 最大标志
#define MAX_TXT 1024                // 最大文本
#define MAX_MSG (MAX_NCK+MAX_TXT+1) // 最大消息
#define MAX_ACK  256                // 最大应答

#endif // _CHATROOM_H
// 聊天室服务器
//
#include "chatroom.h"

// 发送器结构体
typedef struct Sender {
	int  connfd;            // 发送器连接套接字
	char nickname[MAX_NCK]; // 发送器昵称
}	SENDER;

LIST* g_snds = NULL; // 发送器结构体列表
LIST* g_rcvs = NULL; // 接收器套接字列表
LIST* g_msgs = NULL; // 消息包队列

// 发送器结构体列表互斥量
pthread_mutex_t g_mtx_snds = PTHREAD_MUTEX_INITIALIZER;
// 接收器套接字列表互斥量
pthread_mutex_t g_mtx_rcvs = PTHREAD_MUTEX_INITIALIZER;
// 消息包队列互斥量
pthread_mutex_t g_mtx_msgs = PTHREAD_MUTEX_INITIALIZER;
// 消息包队列非空条件变量
pthread_cond_t  g_cnd_msgs = PTHREAD_COND_INITIALIZER;

// SIGINT信号处理函数
void sigint (int signum) {
	printf ("\n服务器:再见!\n");
	exit (0);
}

// 从消息包队列弹出消息
char* pop_msg (void) {
	pthread_mutex_lock (&g_mtx_msgs);
	while (list_empty (g_msgs)){
		pthread_cond_wait (&g_cnd_msgs, &g_mtx_msgs);
	}
	char* msg = (char*)list_pop (g_msgs);
	pthread_mutex_unlock (&g_mtx_msgs);
	return msg;
}

// 向消息包队列压入消息
void push_msg (char* msg) {
	pthread_mutex_lock (&g_mtx_msgs);
	list_push (g_msgs, msg);
	pthread_cond_broadcast (&g_cnd_msgs);
	pthread_mutex_unlock (&g_mtx_msgs);
}

// 发送线程过程函数
void* send_proc (void* arg) {
	// 发送循环
	for (;;) {
		char* msg = pop_msg (); // 从消息包对列中弹出消息
		pthread_mutex_lock (&g_mtx_rcvs);

		// 遍历接收器套接字列表
		list_begin (g_rcvs);
		while (! list_end (g_rcvs)) {
			int* connfd = (int*)list_next (g_rcvs); // 针对每一个接收器

			// 向接收器发送消息包
			if (send (*connfd, msg, (strlen (msg) + 1) * sizeof (msg[0]), 0) == -1) {
				perror ("send");
				close (*connfd);
				*connfd = -1;
				continue;
			}

			// 从接收器接收应答包
			char ack[MAX_ACK];
			ssize_t rb = recv (*connfd, ack, sizeof (ack), 0);
			if (rb == -1) {
				perror ("recv");
				close (*connfd);
				*connfd = -1;
				continue;
			}
			if (rb == 0) {
				printf ("服务器:接收器已关闭连接。\n");
				close (*connfd);
				*connfd = -1;
				continue;
			}
		}

		// 从接收器套接字列表中删除已失效的接收器套接字
		list_remove (g_rcvs, (void*)-1);

		pthread_mutex_unlock (&g_mtx_rcvs);
		free (msg); // 销毁已发送的消息包
	}

	return NULL;
}

// 发送器登入
void login (const SENDER* sender) {
	pthread_mutex_lock (&g_mtx_snds);
	list_push (g_snds, (void*)sender);
	pthread_mutex_unlock (&g_mtx_snds);
}

// 发送器登出
void logout (const SENDER* sender) {
	pthread_mutex_lock (&g_mtx_snds);
	list_remove (g_snds, (void*)sender);
	pthread_mutex_unlock (&g_mtx_snds);
}

// 接收线程过程函数
void* recv_proc (void* arg) {
	SENDER* sender = (SENDER*)arg;
	login (sender);

	// 向消息包队列压入欢迎包
	char* msg = malloc (MAX_MSG * sizeof (char));
	sprintf (msg, "系统> 热烈欢迎%s进入聊天室!", sender->nickname);
	push_msg (msg);

	// 接收循环
	for (;;) {
		// 从发送器接收文本包
		char txt[MAX_TXT];
		ssize_t rb = recv (sender->connfd, txt, sizeof (txt), 0);
		if (rb == -1) {
			perror ("recv");
			break;
		}
		if (rb == 0) {
			printf ("服务器:发送器已关闭连接。\n");

			// 向消息包队列压入告别包
			char* msg = malloc (MAX_MSG * sizeof (char));
			sprintf (msg, "系统> %s挥一挥衣袖,不带走一片云彩...",
				sender->nickname);
			push_msg (msg);
			break;
		}

		// 向消息包队列压入文本包
		char* msg = malloc (MAX_MSG * sizeof (char));
		sprintf (msg, "%s> %s", sender->nickname, txt);
		push_msg (msg);

		// 向发送器发送应答包
		char ack[MAX_ACK] = "OK";
		if (send (sender->connfd, ack, (strlen (ack) + 1) *
			sizeof (ack[0]), 0) == -1) {
			perror ("send");
			break;
		}
	}

	logout (sender);
	close (sender->connfd);
	free (sender);

	return NULL;
}

// 启动发送线程
int start_send (void) {
	printf ("服务器:启动发送线程...\n");

	int error;
	pthread_attr_t attr;

	if ((error = pthread_attr_init (&attr)) != 0) {
		fprintf (stderr, "pthread_attr_init: %s\n",
			strerror (error));
		return -1;
	}

	if ((error = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED)) != 0) {
		fprintf (stderr, "pthread_attr_setdetachstate: %s\n", strerror (error));
		return -1;
	}

	pthread_t tid;
	if ((error = pthread_create (&tid, &attr, send_proc, NULL)) != 0) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_attr_destroy (&attr)) != 0) {
		fprintf (stderr, "pthread_attr_destroy: %s\n", strerror (error));
		return -1;
	}

	return 0;
}

// 启动
int start (unsigned short port) {
	if (signal (SIGINT, sigint) == SIG_ERR) {
		perror ("signal");
		return -1;
	}

	printf ("服务器:创建网络流套接字...\n");

	int sockfd = socket (AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		perror ("socket");
		return -1;
	}

	int on = 1;
	if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) == -1) {
		perror ("setsockopt");
		return -1;
	}

	printf ("服务器:准备地址并绑定...\n");

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons (port);
	addr.sin_addr.s_addr = INADDR_ANY;
	if (bind (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1) {
		perror ("bind");
		return -1;
	}

	printf ("服务器:监听套接字...\n");

	if (listen (sockfd, 1024) == -1) {
		perror ("listen");
		return -1;
	}

	g_snds = list_create ();
	g_rcvs = list_create ();
	g_msgs = list_create ();

	// 启动发送线程
	if (start_send () == -1)
		return -1;

	return sockfd; // 返回监听套接字
}

// 昵称可用否
int verify (const char* nickname) {
	int ok = 1;
	pthread_mutex_lock (&g_mtx_snds);

	// 遍历发送器结构体列表
	list_begin (g_snds);
	while (! list_end (g_snds)) {
		// 针对每一个发送器
		if (! strcmp ((**(SENDER**)list_next (g_snds)).nickname, nickname)) {
			ok = 0;
			break;
		}
	}

	pthread_mutex_unlock (&g_mtx_snds);
	return ok;
}

// 等待客户机连接
int wait_client (int sockfd, char nickname[]) {
	printf ("服务器:等待连接请求...\n");

	// 接受来自客户机的连接请求
	struct sockaddr_in addrcli;
	socklen_t addrlen = sizeof (addrcli);
	int connfd = accept (sockfd, (struct sockaddr*)&addrcli, &addrlen);
	if (connfd == -1) {
		perror ("accept");
		return -1;
	}

	// 从客户机接收标志包
	char tag[MAX_TAG];
	if (recv (connfd, tag, MAX_TAG, 0) == -1) {
		perror ("recv");
		close (connfd);
		return -2;
	}

	char ack[MAX_ACK] = "OK";

	// 发送器
	if (! strncmp (tag, "SNDR", 4))
		if (verify (tag + 4)) {
			printf ("服务器:接受来自%s:%u发送器的连接请求。\n",
					inet_ntoa (addrcli.sin_addr), ntohs (addrcli.sin_port));
			strcpy (nickname, tag + 4);
		}
		else {
			printf ("服务器:拒绝来自%s:%u发送器的连接请求。\n",
					inet_ntoa (addrcli.sin_addr), ntohs (addrcli.sin_port));
			strcpy (ack, "更换昵称再试一次!");
		}
	// 接收器
	else if (! strncmp (tag, "RCVR", 4))
		printf ("服务器:接受来自%s:%u接收器的连接请求。\n",
				inet_ntoa (addrcli.sin_addr), ntohs (addrcli.sin_port));
	// 非法客户机
	else {
		printf ("服务器:拒绝来自%s:%u客户机的连接请求。\n",
				inet_ntoa (addrcli.sin_addr), ntohs (addrcli.sin_port));
		close (connfd);
		return -2;
	}

	// 向客户机发送应答包
	if (send (connfd, ack, (strlen (ack) + 1) * sizeof (ack[0]), 0) == -1) {
		perror ("send");
		close (connfd);
		return -2;
	}

	if (strcmp (ack, "OK")) {
		close (connfd);
		return -2;
	}

	return connfd; // 返回连接套接字
}

// 启动接收线程
int start_recv (int connfd, const char* nickname) {
	printf ("服务器:启动接收线程...\n");

	int error;
	pthread_attr_t attr;

	if ((error = pthread_attr_init (&attr)) != 0) {
		fprintf (stderr, "pthread_attr_init: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED)) != 0) {
		fprintf (stderr, "pthread_attr_setdetachstate: %s\n", strerror (error));
		return -1;
	}

	// 初始化发送器结构体,填入发送器信息,
	// 包括与之连接的套接字
	SENDER* sender = malloc (sizeof (SENDER));
	sender->connfd = connfd;
	strcpy (sender->nickname, nickname);

	// 每个接收线程持有一个发送器结构体,
	// 为该发送器提供服务
	pthread_t tid;
	if ((error = pthread_create (&tid, &attr, recv_proc, sender)) != 0) {
		fprintf (stderr, "pthread_create: %s\n", strerror (error));
		return -1;
	}

	if ((error = pthread_attr_destroy (&attr)) != 0) {
		fprintf (stderr, "pthread_attr_destroy: %s\n", strerror (error));
		return -1;
	}

	return 0;
}

// 运行
int run (int sockfd) {
	for (;;) {
		// 等待客户机连接,返回与客户机连接的套接字
		char nickname[MAX_NCK] = {};
		int connfd = wait_client (sockfd, nickname);
		if (connfd == -1)
			return -1;
		else
		if (connfd == -2)
			continue;

		// 针对来自发送器的连接
		if (strlen (nickname)) {
			// 启动接收线程
			if (start_recv (connfd, nickname) == -1) {
				close (connfd);
				continue;
			}
		}
		// 针对来自接收器的连接
		else {
			// 将与接收器连接的套接字加入接收器套接字列表
			pthread_mutex_lock (&g_mtx_rcvs);
			list_push (g_rcvs, (void*)connfd);
			pthread_mutex_unlock (&g_mtx_rcvs);
		}
	}
}

// 停止
int stop (int sockfd) {
	list_destroy (g_msgs);
	list_destroy (g_rcvs);
	list_destroy (g_snds);

	printf ("服务器:关闭套接字...\n");

	if (close (sockfd) == -1) {
		perror ("close");
		return -1;
	}

	printf ("服务器:再见!\n");

	return 0;
}

// 主函数
int main (int argc, char* argv[]) {
	if (argc < 2) {
		fprintf (stderr, "用法:%s <端口号>\n", argv[0]);
		return -1;
	}

	printf ("********************\n");
	printf ("* 狂聊聊天室服务器 *\n");
	printf ("********************\n");

	// 启动
	int sockfd = start (atoi (argv[1]));
	if (sockfd == -1)
		return -1;

	// 运行
	if (run (sockfd) == -1)
		return -1;

	// 停止
	return stop (sockfd);
}
// 聊天室发送器
//
#include "chatroom.h"

// 启动
int start (const char* ip, unsigned short port, const char* nickname) {
	printf ("发送器:创建网络流套接字...\n");

	int sockfd = socket (AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		perror ("socket");
		return -1;
	}

	printf ("发送器:准备地址并连接...\n");

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons (port);
	addr.sin_addr.s_addr = inet_addr (ip);
	if (connect (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1) {
		perror ("connect");
		return -1;
	}

	// 向服务器发送标志包
	char tag[MAX_TAG] = "SNDR";
	strcat (tag, nickname);
	if (send (sockfd, tag, (strlen (tag) + 1) * sizeof (tag[0]), 0) == -1) {
		perror ("send");
		return -1;
	}

	// 从服务器接收应答包
	char ack[MAX_ACK];
	ssize_t rb = recv (sockfd, ack, sizeof (ack), 0);
	if (rb == -1) {
		perror ("recv");
		return -1;
	}
	if (rb == 0) {
		printf ("发送器:服务器已宕机!\n");
		return -1;
	}

	// 服务器返回错误信息
	if (strncmp (ack, "OK", 2)) {
		printf ("服务器:%s\n", ack);
		return -1;
	}

	return sockfd;
}

// 运行
int run (int sockfd, const char* nickname) {
	printf ("发送器:发送信息...\n");

	for (;;) {
		printf ("%s> ", nickname);

		char txt[MAX_TXT];
		gets (txt); // 读取控制台输入

		if (strspn (txt, " \t\r\n") == strlen (txt)) {
			printf ("发送器:请不要输入空白字符串!\n");
			continue;
		}

		if (! strcmp (txt, "!"))
			break;

		// 向服务器发送文本包
		if (send (sockfd, txt, (strlen (txt) + 1) * sizeof (txt[0]), 0) == -1) {
			perror ("send");
			return -1;
		}

		// 从服务器接收应答包
		char ack[MAX_ACK];
		ssize_t rb = recv (sockfd, ack, sizeof (ack), 0);
		if (rb == -1) {
			perror ("recv");
			return -1;
		}
		if (rb == 0) {
			printf ("发送器:服务器已宕机!\n");
			break;
		}
	}

	return 0;
}

// 停止
int stop (int sockfd) {
	printf ("发送器:关闭套接字...\n");

	if (close (sockfd) == -1) {
		perror ("close");
		return -1;
	}

	printf ("发送器:再见!\n");

	return 0;
}

int main (int argc, char* argv[]) {
	if (argc < 4) {
		fprintf (stderr, "用法:%s <服务器IP地址> <端口号> <昵称>\n", argv[0]);
		return -1;
	}

	printf ("********************\n");
	printf ("* 狂聊聊天室发送器 *\n");
	printf ("********************\n");

	// 启动
	int sockfd = start (argv[1], atoi (argv[2]), argv[3]);
	if (sockfd == -1)
		return -1;

	// 运行
	if (run (sockfd, argv[3]) == -1)
		return -1;

	// 停止
	return stop (sockfd);
}
// 聊天室接收器
//
#include "chatroom.h"

// SIGINT信号处理函数
void sigint (int signum) {
	printf ("\n接收器:再见!\n");
	exit (0);
}

// 启动
int start (const char* ip, unsigned short port) {
	if (signal (SIGINT, sigint) == SIG_ERR) {
		perror ("signal");
		return -1;
	}

	printf ("接收器:创建网络流套接字...\n");

	int sockfd = socket (AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		perror ("socket");
		return -1;
	}

	printf ("接收器:准备地址并连接...\n");

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons (port);
	addr.sin_addr.s_addr = inet_addr (ip);

	if (connect (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1) {
		perror ("connect");
		return -1;
	}

	// 向服务器发送标志包
	char tag[MAX_TAG] = "RCVR";
	if (send (sockfd, tag, (strlen (tag) + 1) * sizeof (tag[0]), 0) == -1) {
		perror ("send");
		return -1;
	}

	// 从服务器接收应答包
	char ack[MAX_ACK];
	ssize_t rb = recv (sockfd, ack, sizeof (ack), 0);
	if (rb == -1) {
		perror ("recv");
		return -1;
	}
	if (rb == 0) {
		printf ("接收器:服务器已宕机!\n");
		return -1;
	}

	return sockfd;
}

// 运行
int run (int sockfd) {
	printf ("接收器:接收信息...\n");

	for (;;) {
		// 从服务器接收消息包
		char msg[MAX_MSG];
		ssize_t rb = recv (sockfd, msg, sizeof (msg), 0);
		if (rb == -1) {
			perror ("recv");
			return -1;
		}
		if (rb == 0) {
			printf ("接收器:服务器已宕机!\n");
			break;
		}

		// 打印消息包
		printf ("%s\n", msg);

		// 向服务器发送应答包
		char ack[MAX_ACK] = "OK";
		if (send (sockfd, ack, (strlen (ack) + 1) * sizeof (ack[0]), 0) == -1) {
			perror ("send");
			return -1;
		}
	}

	return 0;
}

// 停止
int stop (int sockfd) {
	printf ("接收器:关闭套接字...\n");

	if (close (sockfd) == -1) {
		perror ("close");
		return -1;
	}

	printf ("接收器:再见!\n");

	return 0;
}

int main (int argc, char* argv[]) {
	if (argc < 3) {
		fprintf (stderr, "用法:%s <服务器IP地址> <端口号>\n", argv[0]);
		return -1;
	}

	printf ("********************\n");
	printf ("* 狂聊聊天室接收器 *\n");
	printf ("********************\n");

	// 启动
	int sockfd = start (argv[1], atoi (argv[2]));
	if (sockfd == -1)
		return -1;

	// 运行
	if (run (sockfd) == -1)
		return -1;

	// 停止
	return stop (sockfd);
}
CC = gcc
RM = rm
CFLAGS = -c -Wall -I../inc

all: chatroom sender receiver

chatroom: chatroom.o list.o
	$(CC) $^ -lpthread -o ../bin/$@

chatroom.o: chatroom.c
	$(CC) $(CFLAGS) $^

list.o: list.c
	$(CC) $(CFLAGS) $^

sender: sender.o
	$(CC) $^ -o ../bin/$@

sender.o: sender.c
	$(CC) $(CFLAGS) $^

receiver: receiver.o
	$(CC) $^ -o ../bin/$@

receiver.o: receiver.c
	$(CC) $(CFLAGS) $^

clean:
	$(RM) *.o \
	../bin/chatroom \
	../bin/sender \
	../bin/receiver