服务器:
#include "chat.h"
void *stranspond(void *arg);
int fd_A[MAX_CLIENT];
int conn_amount;
struct user_list user;
int main()
{
int server_fd;
int newfd;
int i;
int result;
int maxsock;
int sin_size;
char buffer[BUFFER_SIZE];
struct user_list *tmp;
struct list_head *pos;
struct list_head *q;
struct sockaddr_in s_add;
struct sockaddr_in c_add;
/*
struct timeval
{
__time_t tv_sec; 秒
__suseconds_t tv_usec; 微秒
};
*/
struct timeval tv;
pthread_t a_thread;
fd_set set_fd;
/*初始化链表头*/
INIT_LIST_HEAD(&user.mylist);
printf("hello, welcome to my server! \n");
/*
初始化一个用于监听的socket,AF_INET表示的是TCP/IP协议族,SOCK_STREAM代表的是数据流。第二个参数还包括数据报SOCK_DGRAM。
*/
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
printf("socket fail\n");
return -1;
}
printf("socket ok\n");
/*
bzero()函数用于套接字地址清零。INADDR_ANY表示的是本机网口的一个地址。AF_INET表示的是TCP/IP协议族。
*/
bzero(&s_add, sizeof(struct sockaddr_in));
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = INADDR_ANY;
s_add.sin_port = htons(MYPORT);
/*绑定监听socket*/
if (bind(server_fd, (struct sockaddr *)(&s_add), sizeof(struct sockaddr)) == -1) {
printf("bind fail \n");
return -1;
}
printf("bind ok \n");
/*设置监听socket,MAX_CLIENT表示最大的监听数,当连接数目超过MAX_CLIENT时拒绝连接*/
if (listen(server_fd, MAX_CLIENT) == -1) {
printf("listen fail \n");
return -1;
}
printf("listen ok \n");
conn_amount = 0;
sin_size = sizeof(struct sockaddr_in);
maxsock = server_fd;
while(1)
{
/*初始化fd_set集合*/
FD_ZERO(&set_fd);
FD_SET(server_fd, &set_fd);
tv.tv_sec = 10;
tv.tv_usec = 0;
/*判断描述符fd是否在给定的描述符集fdset中,通常配合select函数使用,由于select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。
当描述符fd在描述符集fdset中返回非零值,否则,返回零。
*/
if (FD_ISSET(server_fd, &set_fd)) {
/*
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
三方握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。cliaddr是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出参数,传入的是调用者提供的缓冲区cliaddr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给cliaddr参数传NULL,表示不关心客户端的地址。
*/
newfd = accept(server_fd, (struct sockaddr *)&c_add, &sin_size);
if (newfd <= 0) {
printf("accept fail\n");
}
if (conn_amount < MAX_CLIENT) {
fd_A[conn_amount] = newfd;
printf("new connection client[%d] %d:%d\n",
conn_amount, ntohl(c_add.sin_addr.s_addr), c_add.sin_port);
/*
使用用户信息链表,user_list,将客户端的信息存储到链表中。struct user_list *tmp,需要分配tmp空间,注意tmp 为一个指针。
*/
tmp = (struct user_list *)malloc(sizeof(struct user_list));
tmp->cli_addr = c_add;
tmp->cli_sock_fd = fd_A[conn_amount];
tmp->server_sock_fd = server_fd;
tmp->cli_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
tmp->amount = conn_amount;
list_add_tail(&tmp->mylist, &user.mylist);
conn_amount++;
if (newfd > maxsock) {
maxsock = newfd;
}
} else {
close(newfd);
}
}
for (i = 0; i < MAX_CLIENT; i++) {
if(fd_A[i] != 0) {
FD_SET(fd_A[i], &set_fd);
}
}
/*
FD_ISSET()函数判断监听描述符是否准备好。
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
参数说明:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1。
fd_set *readfds 是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的。如果有一个文件可读,返回大于0。如果没有可读文件,再根据timeout查看是否超时,超时返回0。如果返回负值,则表示select错误。
fd_set *writefds 也是指向fd_set结构的指针,监视写变化。返回值大于0可写。若没有可写,查看timeout是否超时,超时返回0.返回0表示select错误。
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval* timeout是select的超时时间,它可以使select处于三种状态。
一、传入NULL表示将select设置为阻塞状态,一直等到文件描述符集合中某个描述符发生变化。
二、传入时间为0秒0毫秒,将select设置为非阻塞状态,不管文件描述符是否发生变化,都立刻返回执行。文件描述符无变化返回0,有变化返回一个正值。
三、将timeout设置为大于0的值。select在timeout内阻塞,有事件描述符发生变化时返回大于0的值。超时后,不管是否发生变化,都立刻执行返回。又变化返回正值,无变化返回0。
*/
if (FD_ISSET(server_fd, &set_fd)) {
printf("wait a qq come:\n");
result = select(maxsock + 1, &set_fd, NULL, NULL, &tv);
if (result < 0) {
printf("select fail");
break;
} else if (result == 0) {
printf("time out\n");
continue;
} else {
for (i = 0; i < conn_amount; i++) {
if (FD_ISSET(fd_A[i], &set_fd)) {
/*
int send( SOCKET s, const char FAR *buf, int len, int flags );
参数说明:
第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。需要对应使用。
套接字s中有一个发送缓冲区。如果len > s的发送缓冲区,则返回SOCKET_ERROR。如果len <= s发送缓冲区,检查是否在发送中。
是:把协议中的数据发送完
否:s的发送缓冲区未开始发送,或者s发送缓冲区无数据。
比较len与s的剩余空间:
len > s send等待s缓冲区发送完
len < s 把buf中的数据拷贝到缓冲区中
int recv( SOCKET s, char FAR *buf, int len, int flags);
参数说明:
第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
不论是客户还是服务器应用程序都用recv函数来向TCP连接的另一端接收数据。
recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR。
如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中。(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)
recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
*/
result = recv(fd_A[i], tmp->qq_num_from, sizeof(tmp->qq_num_from), 0);
printf("wait anther qq:\n");
recv(fd_A[i], tmp->qq_num_to, sizeof(tmp->qq_num_to), 0);
/*
int pthread_create((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
主要,是creat**e**!
参数说明:
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。一般设置为NULL.
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。只能是一个参数!
*/
result = pthread_create(&a_thread, NULL, stranspond, (void *)tmp);
if (result != 0) {
perror("pthread_creat");
}
printf("you qq:%s: she qq:%s\n", tmp->qq_num_from, tmp->qq_num_to);
}
}
}
}
}
}
/*
该线程用于数据转发
*/
void *stranspond(void *arg)
{
(void) signal(SIGINT, ouch);
int send_fd;
int server_fd;
int result;
int file_in;
int new_sock;
int loop = 1;
char *from;
char *to;
char buffer[BUFFER_SIZE];
char buffer2[BUFFER_SIZE];
char warn_wait[] = "user:%s don't connect!please wait 5 second\n";
const char notes[] = "%s:\n %s";
const char user_file[] = "./log/%s.in";
char warn[64];
char log_name[21];
int send_len;
struct sockaddr_in cli_addr;
struct list_head *she_list;
struct user_list *client;
struct user_list *unit_data;
client = (struct user_list *)arg;
from = (*client).qq_num_from;
to = (*client).qq_num_to;
send_fd = (*client).cli_sock_fd;
server_fd = (*client).server_sock_fd;
cli_addr = (*client).cli_addr;
/*将分配的端口加1,*/
(*client).cli_addr.sin_port++;
sprintf(log_name, user_file, from);
printf("log_name=%s\n", log_name);
file_in = open(log_name, O_CREAT|O_WRONLY, S_IWUSR|S_IXOTH);
if(file_in < 0) {
printf("open fail\n");
}
send(send_fd, "i love you", 11, 0);
send_len = send(send_fd, client, sizeof(struct user_list), 0);
printf("you send :%d bytes client.qq_num_from=%s \n", send_len, from);
printf("connecting......\n");
/*创建一个新的socket用于连接客户端的socket*/
new_sock = socket(AF_INET, SOCK_STREAM, 0);
if (connect(new_sock, (struct sockaddr *)(&cli_addr), sizeof(struct sockaddr)) == -1) {
printf("connect fail \n");
}
printf("you connect qq=%s---port=%d\n", from, (*client).cli_addr.sin_port);
sprintf(warn, warn_wait, to);
client->cli_receive_fd = new_sock;
/*
查找发送对象是否在线。
list_for_each(pos, head) 从head节点开始(不包括head节点!)遍历它的每一个节点。
list_entry(ptr, type, member) 获取type类型结构体的起始指针。
*/
while (loop) {
list_for_each(she_list, &user.mylist) {
unit_data = list_entry(she_list, struct user_list, mylist);
if(strcmp(to, unit_data->qq_num_from) == 0) {
loop = 0;
break;
}
}
printf("%s", warn);
send(new_sock, warn, sizeof(warn), 0);
sleep(5);
}
send(new_sock, "she is connect !\n", sizeof("she is connect !\n"), 0);
printf("you qq:%s she qq:%s and cli_receive_fd=%d\n",
from, to, client->cli_receive_fd);
printf("new_sock = %d,", new_sock);
while(strncmp("end", buffer, 3) != 0)
{
result = recv(new_sock, buffer, sizeof(buffer), 0);
if(result < 0) {
printf("recv error!\n");
}
sprintf(buffer2, notes, from, buffer);
write(file_in, buffer2, sizeof(buffer2));
send_len = send(unit_data->cli_receive_fd, buffer2, sizeof(buffer), 0);
if (send_len < 0) {
printf("send to she error");
pthread_exit(NULL);
}
if(buffer[0] != '\0') {
printf("\nQQ:%s send:%s", from, buffer2);
}
}
list_del(she_list);
free(unit_data);
close(file_in);
close(new_sock);
close(send_fd);
conn_amount--;
pthread_exit(NULL);
}
客户端:
#include "chat.h"
void *receive(void *arg);
int new_fd;
int receive_fd;
pthread_t reveive_pthread;
int main()
{
int client_fd;
int result;
int sen;
char qq_num[10];
char qq_num2[10];
char buffer[BUFFER_SIZE];
struct user_list me;
struct sockaddr_in s_add;
unsigned short portnum = 0x8899;
printf("Hello,welcome to client \n");
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
printf("socket fail \n");
return -1;
}
printf("socket ok \n");
/*s_add为server监听socket的地址与端口*/
bzero(&s_add, sizeof(struct sockaddr_in));
s_add.sin_family = AF_INET;
s_add.sin_addr.s_addr = inet_addr("127.0.0.1");
s_add.sin_port = htons(portnum);
printf("s_addr = %#x ,port = % \n",s_add.sin_addr.s_addr,s_add.sin_port);
/*
int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
sock_fd是需要进行连接的描述符。serv_addr是服务端监听socket的地址指针。addrlen是结构sockaddr_in的长度。
成功返回0,失败返回-1,并设置errno。
*/
result = connect(client_fd, (struct sockaddr *)(&s_add), sizeof(struct sockaddr));
if (result == -1) {
printf("connect fail \n");
}
printf("you connect server\n");
printf("please enter you qq number:\n");
fgets(qq_num, 10, stdin);
sen = strlen(qq_num);
qq_num[sen-1] = '\0';
sen = send(client_fd, &qq_num, strlen(qq_num), 0);
if(sen < 0) {
printf("send fail\n");
}
printf("please enter you she number:\n");
fgets(qq_num2, 10, stdin);
sen = strlen(qq_num2);
qq_num2[sen - 1] = '\0';
sen = send(client_fd, &qq_num2, strlen(qq_num2), 0);
if(sen < 0) {
printf("send fail\n");
}
printf("send qq ok\n");
/*
extern void *memset(void *buffer, int c, int count)
buffer为指针或是数组,c是赋给buffer的值,count是buffer的长度.
这个函数在socket中多用于清空数组.
*/
memset(buffer, 0, sizeof(buffer));
recv(client_fd, buffer, sizeof(buffer), 0);
printf("%s\n",buffer);
/*
得到一个新的user_list结构me,使用me中的cli_addr进行地址绑定,启动一个新的线程专门用于接收消息
recv()函数不仅可以用于接收数组,也可以用于接收其他数据类型,例如结构。但是要特别注意,两边的数据长度一定要相同,避免数据的丢失。之前修改了server中的user_list却没有修改client的结构user_list,导致bind失败,原因是后面地址位丢失。
*/
int recv_len = recv(client_fd, &me, sizeof(me), 0);
if (recv_len < 0) {
printf("recv mylist error!");
}
printf("recv %d bytes \n new_port = %d\n", recv_len, me.cli_addr.sin_port);
new_fd = socket(AF_INET, SOCK_STREAM, 0);
result = bind(new_fd, (struct sockaddr *)(&me.cli_addr), sizeof(struct sockaddr));
if (result == -1) {
printf("bind fail \n");
}
result = listen(new_fd, MAX_CLIENT);
if (result == -1) {
printf("listen fail \n");
}
int sin_size = sizeof(struct sockaddr_in);
receive_fd = accept(new_fd, (struct sockaddr *)(&me.cli_addr), &sin_size);
if (receive_fd <= 0) {
printf("accept fail\n");
}
result = pthread_create(&reveive_pthread, NULL, receive, NULL);
if (result == -1) {
printf("pthread_create fail\n");
}
printf("Now! you can chat with she\n"); /*send model*/
while(strncmp("end", buffer, 3) != 0)
{
fgets(buffer, BUFFER_SIZE, stdin);
if(buffer[0] != '\0') {
sen = send(receive_fd, buffer, sizeof(buffer), 0);
if(sen < 0) {
printf("send fail\n");
}
printf("\nQQ:%s :%s", qq_num, buffer);
}
}
close(client_fd);
close(receive_fd);
printf("i am dead \n");
exit(EXIT_SUCCESS);
return 0;
}
void *receive(void *arg)
{
(void) signal(SIGINT, ouch);
char buffer[BUFFER_SIZE];
int result;
while(1)
{
result = recv(receive_fd, buffer, sizeof(buffer), 0);
if(result < 0) {
printf("recv error!\n");
exit(EXIT_FAILURE);
}
if(buffer[0] != '\0') {
printf("%s", buffer);
}
}
exit(EXIT_SUCCESS);
}
头文件:
#ifndef __CHAT_H
#define __CHAT_H
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
#define MYPORT 0x8899
#define MAX_CLIENT 5
#define BUFFER_SIZE 1024
#define NAME_SIZE 10
struct user_list{
int cli_sock_fd;
int cli_receive_fd;
int server_sock_fd;
int amount;
char qq_num_from[NAME_SIZE];
char qq_num_to[NAME_SIZE];
struct sockaddr_in cli_addr;
struct list_head mylist;
};
void ouch(int sig)
{
printf("ouch! i got signal %d\n", sig);
(void) signal(SIGINT, SIG_DFL);
kill(getpid(), SIGINT);
}
#endif
“`