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

2019-07-12 23:02发布

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

服务器代码:

#include #include #include #include #include #include #include #include #include #include #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... "); 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... "); /*监听连接*/ if(listen(sockfd, BACKLOG) < 0) { fprintf(stderr, "Listen Error: %s", strerror(errno)); exit(1); } printf(" listen... "); while(1) { size = sizeof(struct sockaddr); /*阻塞服务器,获取连接*/ printf(" accept... "); if((listenfd = accept(sockfd, (struct sockaddr*)&c_addr, &size)) < 0) { fprintf(stderr, "accept error: %s", strerror(errno)); exit(1); } /*填充传入线程的参数*/ printf("Creat pthread arg "); 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 ", inet_ntoa(client.sin_addr)); /*添加用户到全局用户表中*/ user[u_len].fd = fd; user[u_len].name = 'a' + u_len; /*打印新增的用户*/ printf("user: %c, fd: %d ", 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 ", num); if(num == 0 || num == -1) { close(fd); perror("Client disconnected. "); break; } printf("[%d] INFO:%s ", 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 #include #include #include #include #include #include #include #include #include #include #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 "); exit(1); } } else { if((he = gethostbyname(argv[1])) == NULL) { perror("gethostbyname(argv[1]) error "); 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 "); 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 "); while(1) { if((n = recv(sockfd, recv_l, BUFFER, 0)) > 0) { recv_l[n] = 0; printf(" [recvice] %s ", recv_l); /*清空接收缓冲区*/ memset(recv_l, 0, strlen(recv_l) - 1); } else { printf("revice error "); } } } int get_line(char* msg) { int i = 0; char temp; printf("Please input message:"); fflush(stdout); while(1) { temp = getchar(); if(temp == ' ' || temp == ' ') {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库不是Linux系统默认的库,连接时需要使用静态库libthread.a. 编译时加上参数 -lpthread参数即可解决问题。 3>int fflush(FILE *fp) 强制冲洗一个流。 当一个流涉及到一个终端时,通常使用的是行缓冲,即当在输入或者输出时,遇到换行符才执行IO操作(输入到文件/输出到终端),所以为了强制输出,我们可以冲洗一个流,强制其输出到终端

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

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