嵌入式Linux网络编程 之 多线程聊天

题目要求:编写一个网络聊天程序,要求采用数据流的套接口编程
程序分为服务端与客户端
服务端最大同时连接10个客户端
服务端可以响应多个客户端的请求,每个客户端直接可以相互通信,由服务器实现转发。服务器端显示所有客户端的通信
客户端通过用户名实现不同用户间的通信(发送消息格式:用户名 消息内容)

服务器代码:

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 8848//为服务端定义一个端口
#define BACKLOG 10//服务端最大的连接数
#define BUFFER 1024
#define MAXUSER 9

int select_user(char name);
void process_cli(int fd, struct sockaddr_in client);
void* creat_conn(void* arg);

struct _tag_arg{
	int fd;
	struct sockaddr_in client;
};

/*全局用户列表*/
struct _tag_user{
	char name;
	int fd;
}user[MAXUSER+1];

static int u_len = 0;

int main(int argc, char *argv[])
{
	int sockfd, listenfd;
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	int size;
	int opt;//设置socket的状态的参数
	struct _tag_arg *arg;//传入线程的参数(不止一个变量)
	pthread_t thread;
	
	/*创建套接字*/
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		fprintf(stderr, "Sock Error: %s", strerror(errno));
		exit(1);
	}
	printf("socket...\n");
	
	opt = SO_REUSEADDR;
	/*设置套接字允许重用本地地址和端口*/
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	
	bzero(&s_addr, sizeof(s_addr));
	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(PORT);
	s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	/*绑定套接字与描述符*/
	if(bind(sockfd, (struct sockaddr*)&s_addr, sizeof(s_addr)) == -1)
	{
		fprintf(stderr, "bind error: %s", strerror(errno));
		exit(1);
	}
	printf("Bind...\n");
	
	/*监听连接*/
	if(listen(sockfd, BACKLOG) < 0)
	{
		fprintf(stderr, "Listen Error: %s", strerror(errno));
		exit(1);
	}
	printf(" listen...\n");
	
	while(1)
	{
		size = sizeof(struct sockaddr);
		
		/*阻塞服务器,获取连接*/
		printf("  accept...\n");
		if((listenfd =  accept(sockfd, (struct sockaddr*)&c_addr, &size)) < 0)
		{
			fprintf(stderr, "accept error: %s", strerror(errno));
			exit(1);
		}
		
		/*填充传入线程的参数*/
		printf("Creat pthread arg\n");
		arg = malloc(sizeof(struct _tag_arg));
		arg->fd = listenfd;
		memcpy((void*)&(arg->client), &c_addr, sizeof(c_addr));
		
		/*创建线程*/
		if(pthread_create(&thread, NULL, creat_conn, (void*)arg))
		{
			fprintf(stderr, "Creat thread failed!: %s", strerror(errno));
			break;		
		}
	}
	close(sockfd);
	exit(0);
}

void* creat_conn(void* arg)
{
	int tid = pthread_self();
	struct _tag_arg *n_arg = arg;
	
	process_cli(n_arg->fd, n_arg->client);
}

void process_cli(int fd, struct sockaddr_in client)
{
	int num, i, ufd;
	char recvbuf[BUFFER], sendbuf[BUFFER];
	char name;
	
	int tid = pthread_self();
	/*打印客户IP*/
	printf("Client Ip: %s\n", inet_ntoa(client.sin_addr));

	/*添加用户到全局用户表中*/
	user[u_len].fd = fd;
	user[u_len].name = 'a' + u_len;
	
	/*打印新增的用户*/
	printf("user: %c, fd: %d\n", user[u_len].name, user[u_len].fd);
	
	if(u_len++ > MAXUSER) u_len = 0;
	
	while(1)
	{
		memset(recvbuf, 0, BUFFER);
		/*等待接收数据*/
		num = recv(fd, recvbuf, BUFFER, 0);
		printf("num = %d\n", num);
		if(num == 0 || num == -1)
		{
			close(fd);
			perror("Client disconnected.\n");
			break;
		}
		
		printf("[%d] INFO:%s\n", tid, recvbuf);
		fflush(stdout);
		
		/*获得客户端的用户名*/
		name = recvbuf[0];
		/*用户名为x, 则进行群发*/
		if(name == 'x')
		{
			for(i=0; i<=MAXUSER; i++)
			{
				if(user[i].fd > 0)
				send(user[i].fd, recvbuf, strlen(recvbuf), 0);
			}
		}
		/*选出要转发到的用户*/
		ufd = select_user(name);
		
		if(ufd > 0)
		{
			send(ufd, recvbuf, strlen(recvbuf), 0);
		}
		
		
	}
}

int select_user(char name)
{
	int i;
	int ret_fd = 0;
	
	for(i=0; i<= MAXUSER; i++)
	{
		if(user[i].name == name)
		ret_fd = user[i].fd;
	}
	
	return ret_fd;
}


客户端代码:

#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <pthread.h>

#define PORT 8848//服务端定义的端口
#define BUFFER 1024

void* send_pro(void*);
void* recv_pro(void*);
int get_line(char* msg);

int main(int argc, char* argv[])
{
	int sockfd;
	char* default_ip = "127.0.0.1";
	struct sockaddr_in s_addr;
	struct hostent* he;
	pthread_t th1, th2;
	
	if(argc != 2)
	{
		/*gethostbyname()函数现已被替代*/
		if((he = gethostbyname(default_ip)) == NULL)
		{
			perror("gethostbyname(default_ip) error\n");
			exit(1);
		}
	}
	else
	{
		if((he = gethostbyname(argv[1])) == NULL)
		{
			perror("gethostbyname(argv[1]) error\n");
			exit(1);
		}
	}

	/*创建套接字*/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	
	if(sockfd < 0)
	{
		fprintf(stderr, "socket() error : %s", strerror(errno));
		exit(1);
	}
	/*填充服务器地址*/
	bzero(&s_addr, sizeof(s_addr));
	s_addr.sin_family = AF_INET;
	s_addr.sin_port = htons(PORT);
	s_addr.sin_addr = *((struct in_addr *)he->h_addr);
	
	/*连接服务器*/
	if(connect(sockfd, (struct sockaddr*)&s_addr, sizeof(s_addr)) == -1)
	{
		fprintf(stderr, "connect() error : %s", strerror(errno));
		exit(1);
	}
	
	/*创建接收线程*/
	if(pthread_create(&th1, NULL, recv_pro, (void*)&sockfd))
	{
		perror("recv pthread_create() error");
		close(sockfd);
		exit(1);
	}
	/*创建发送线程*/
	if(pthread_create(&th2, NULL, send_pro, (void*)&sockfd))
	{
		perror("send pthread_create() error");
		close(sockfd);
		exit(1);
	}
	
	pthread_join(th1, NULL);
	pthread_join(th2, NULL);
	exit(0);
}

void* send_pro(void* arg)
{
	char send_l[BUFFER];
	int n;
	int len;
	int sockfd = *((int *)arg);
	
	printf("send to server\n");
	
	while(1)
	{
		while((len = get_line(send_l)) != 0)
		{
			send(sockfd, send_l, strlen(send_l), 0);
		}
	}
}

void* recv_pro(void* arg)
{
	char recv_l[BUFFER];
	int n;
	int sockfd = *((int *)arg);
	
	printf("recv from server\n");
	
	while(1)
	{
		if((n = recv(sockfd, recv_l, BUFFER, 0)) > 0)
		{
			recv_l[n] = 0;
			printf("\n[recvice] %s\n", recv_l);
			/*清空接收缓冲区*/
			memset(recv_l, 0, strlen(recv_l) - 1);
		}
		else
		{
			printf("revice error\n");
		}
		
		
	}
}

int get_line(char* msg)
{
	int i = 0;
	char temp;
	printf("Please input message:");
	fflush(stdout);
	while(1)
	{
		temp = getchar();
		if(temp == '\r' || temp == '\n')
		{return i;}
		msg[i] = temp;
		/*回车发送*/
		if(msg[i] == 13)
		{
			break;
		}
		fflush(stdout);
		i++;
	}
}



实现效果:

服务器:

客户端a收到客户端b 的信息:
a:

b:


服务器实现细节及遇到的问题:

1> setsockopt : 获取或者设置与某个套接字关联的选项。参看博客: http://blog.csdn.net/l_yangliu/article/details/7086256#reply
2> 线程: 创建线程 pthread_creat()
编译代码时,出现错误: undefined reference to pthread_create 产生了连接错误。但是明明包含了该函数所需要的头文件<pthread.h>?
原因: pthread库不是Linux系统默认的库,连接时需要使用静态库libthread.a. 编译时加上参数 -lpthread参数即可解决问题。
3>int fflush(FILE *fp)
强制冲洗一个流。
当一个流涉及到一个终端时,通常使用的是行缓冲,即当在输入或者输出时,遇到换行符才执行IO操作(输入到文件/输出到终端),所以为了强制输出,我们可以冲洗一个流,强制其输出到终端

客户端实现细节及遇到的问题

1> 等待子线程结束或返回
int pthread_join(pthread_t thread, void **rval_ptr);
调用线程将一直阻塞,直到指定线程调用pthread_exit,从启动例程中返回或者被取消。


  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值