题目要求:编写一个网络聊天程序,要求采用数据流的套接口编程
程序分为服务端与客户端
服务端最大同时连接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,从启动例程中返回或者被取消。