经过好几天的日夜奋斗,总算把这个聊天室给做出来了,虽然说不上多好,但也是这几天从早到晚劳动的成功,所以就写这篇博文来记录一下啦。别的不敢说,确保能用就是了,完整代码在最后哦~
当然啦,如果有幸被转发,还请注明来处哈~
一、功能
这个Linux下C版本的多人网络聊天室具备以下几个基本功能(或者说需求):
(一)C/S模式,IPv4的TCP通信;
(二)客户端登录需要账号密码,没有账号需要注册;
(三)服务器每接收到一个客户端的连接,就产生一个线程为其服务;
(四)进入聊天室后,客户端可以选择群聊(发给所有人),也可以选择私聊(发给指定账号的人);
(五)服务器可以限定同时登录聊天室的账号的最大数量;
(六)用sqlite3创建保存用户账号信息的数据库;
(七)客户端(服务器端要的话应该也可以)自动获取本地主机未被占用的端口号以连接服务器。
由于时间关系,原本设定的用户登录或退出时通知所有人和保存聊天记录等功能,笔者就没能添加进去。若有兴趣,读者可参考以下想法进行添加:“用户登录或退出时通知所有人”这点可以在用户确认登录后但尚未进入聊天室时通过send/recv实现,而“保存聊天记录”这点就像保存用户账号信息一样保存到数据库(当然更简单的方法是保存到普通文件中)。另外,也有一大点笔者心有余而力不足,有大佬路过时还请指教指教,就是多线程之间交互的问题,例如主线程如何等待所有子线程正常退出等,这一块我不熟悉~
二、主要功能实现分析
(一)网络通信基本步骤
简单来讲就是:客户端——创建套接字,配置网络结构体,绑定,请求连接,正常通信;服务器——创建套接字,配置网络结构体,绑定,监听,接受连接,正常通信。至于网络通信基本原理详细点的介绍可以看我这篇博文介绍——网络通信基本原理。
在这里要注意以下两点:
一个是配置网络结构体,这点其实可以在man手册看到相关(结构体)说明,具体来说就是设定端口号、IP协议(IPv4/IPv6)以及通信对象的IP地址,配置网络结构体是为了后续bind服务的。
//配置网络结构体
struct sockaddr_in myaddr;
myaddr.sin_port = htons(50000); //应用层端口号
myaddr.sin_family = AF_INET; //IPv4协议
myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//通信对象的IP地址,0.0.0.0表示所有IP地址,也可以指定IP
另一个是所谓的“自动获取本地主机未被占用的端口号”。由于笔者只是在自己一台电脑上开多个虚拟终端来运行client,连接的服务器在本地主机(127.0.0.1),而我又不想手动地一个客户端给他传递一个端口号,所以就想到了这个方法。具体来讲,就是从50001开始随便一个一个去尝试bind,绑定失败就下一个,绑定成功的话那就它了。至于为什么是50001开始呢,一个依据是《计算机网络》中说到客户端使用的端口号为49152~65535,然后我就在这个范围内选个中意的范围了。
portnum = 50001;
while(1){
if(portnum > 65535){
printf("bind error because of no port number available!\n");
return -1;
}
(.......配置网络结构体)
ret = bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
if(ret == -1) {
continue;
}
else{
printf("bind successfully!\n");
break;
}
}
(二)用户账号信息如何验证、存储
讲到这点,我们得先看看数据库方面的知识了,这里我用的是SQLite,其操作简单讲就是增、删、查、改这些,具体地C接口在这个程序中我使用的有sqlite3_open_v2()、sqlite3_exec()、sqlite3_get_table()。其实说到数据库,我也是在做这个小项目过程中现学现卖的,所以就不多班门弄斧了,不过一些基本操作咱可以看这个网站。在这里我就介绍下我怎么存储怎么验证。
(1)存储
说到存储,在我这个小项目里那就只能是在注册过程才会用到了,而注册是在客户端进行,所以要先获取ID信息(昵称和密码),然后发送给服务器。服务器读取用户账号信息表格并分配一个尚未使用的ID,然后将账号信息(ID,name,passwd)插入到数据库,而插入成功了也就是说注册成功了,那就要返回一个注册成功标志给客户端。
//这个代码段是用来计算用户账号信息表中有多少条记录的,将这个数量加上起始的100的结果作为账号分配,就能使得账号不重复了。
int j = 100;
char *sql1 = "select * from hwy_id_sheet;";
sqlite3_get_table(db,sql1,&azResult,&nrow,&ncolumn,&errmsg);
j = j+ nrow;
memset(ack_ID_info,0,4);
sprintf(ack_ID_info,"%d",j);//---itoa
(2)验证登录
验证起始也跟前面“存储”差不多,就是:客户端获取登录信息(ID,passwd)–发送给服务器–服务器在用户账号信息表中查找是否有相关记录,有则代表登录成功,没有就代表登录失败–返回登录状态–若登录成功则也返回(name)。
sprintf(sql,"select * from hwy_id_sheet where id = '%s' and passwd = '%s';",
client_ID_info.id,client_ID_info.passwd);
sqlite3_get_table(db,sql,&azResult,&nrow,&ncolumn,&errmsg);
(三)群聊OR私聊
这好办,程序中,当写入的聊天信息中“目标ID”为“999”时发给所有人,也就是所谓的群聊;当写入的聊天信息中“目标ID”为其他ID如“101”时,则发给对应用户。
void SendMsgToAll(char *msg)
{
int i;
for(i=0;i<CLIENT_MAX;i++){
if(id_to_fd[i].client_fd != 0){
printf("sendto%s\n",id_to_fd[i].client_id);
printf("%s\n",msg);
send(id_to_fd[i].client_fd,msg,strlen(msg),0);
}
}
}
void SendMsgToSb(int destfd,char *msg)
{
int i;
for(i=0;i<CLIENT_MAX;i++){
if(id_to_fd[i].client_fd == destfd ){
printf("sendto%s\n",id_to_fd[i].client_id);
printf("%s\n",msg);
send(destfd,msg,strlen(msg),0);
break;
}
}
}
(四)服务器端与客户端对应的fd和ID如何绑定?
因为服务器端accept客户端后会产生client_sockfd,之后服务器就用这个client_sockfd来与客户端通信,然而我们私聊时是无法得知服务器给某个客户端分配了哪一个client_sockfd的,只能通过ID来指定通信用户,所以就产生了这个问题了。至于怎么绑定,看下面结构体就知道了~
//id--fd结构体类型
typedef struct
{
int client_fd;
char client_id[4];
}client_id_to_fd;
(1)服务器每接收到客户端连接时就会产生一个对应的fd,这事写进client_fd中;
(2)服务器验证客户端登录账号成功时会获取一个正确的ID,这时再写进去。
由此也就实现了这一绑定了,之后怎么通信,看上面步骤(三)。
三、完整代码
(1)头文件–hwy_network_chat.h
#ifndef __HWY_NETWORK_CHAT_H__
#define __HWY_NETWORK_CHAT_H__
//聊天室中能注册登陆的最大人员数量
#define PEOPLE_NUM_MAX 10
//聊天信息(发送结构体)长度
#define CHAT_STRUCT_SIZE (POSITION_CONTENT+128)
//客户端最大连接数量
#define CLIENT_MAX 3
#define POSITION_SELFID 0
#define POSITION_NAME (4+POSITION_SELFID)
#define POSITION_DESTID (4+POSITION_NAME)
#define POSITION_TIME (4+POSITION_DESTID)
#define POSITION_CONTENT (26+POSITION_TIME)
//聊天室中人员身份信息
typedef struct
{
char id[4]; //登陆帐号
char name[4]; //昵称
char passwd[8];//登陆密码
}hwy_people_t;
//聊天消息之发送的格式
typedef struct
{
char self_id[4]; //自身ID
char name[4]; //自身昵称
char dest_id[4]; //目标ID
char time[26]; //发送时间
char content[128]; //发送内容
}hwy_send_msg_t;
//聊天消息之接收的格式
typedef struct
{
char who[4]; //发送者
char time[26]; //发送时间
char content[128]; //发送内容
}hwy_recv_msg_t;
#endif
(2)client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include "hwy_network_chat.h"
#include <stdlib.h>
//登录者帐号信息
static hwy_people_t client_id;
/*
* 功能:建立网络通信的初始化工作,包括创建套接字和绑定可用端口号
* 输入参数:无
* 输出参数:成功返回用于网络通信的套接字,失败返回-1
*/
int sock_init(void)
{
int ret;
int portnum;//端口号
//创建套接字--设置通信为IPv4的TCP通信
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket failed");
return -1;
}
//配置网络结构体
/*从50001开始,查找主机中尚未被占用的端口号,然后绑定
*/
struct sockaddr_in myaddr;
portnum = 50001;
while(1){
if(portnum > 65535){
printf("bind error because of no port number available!\n");
return -1;
}
myaddr.sin_port = htons(portnum++); //应用层端口号
myaddr.sin_family = AF_INET; //IPv4协议
myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//通信对象的IP地址,0.0.0.0表示所有IP地址
//绑定套接字和结构体----主机自检端口号是否重复,IP是否准确
ret = bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
if(ret == -1)
{
continue;
}
else{
printf("bind successfully!\n");
break;
}
}
return sockfd;
}
/*
*功能:连接服务器
*输入参数:套接字
*输出参数:连接成功与否的标志,成功返回1,失败返回0
* */
int sock_client(int sockfd)
{
//连接服务器
struct sockaddr_in srcaddr;
srcaddr.sin_port = htons(50000);//服务器的应用层端口号
srcaddr.sin_family = AF_INET;//服务器的IPv4协议
srcaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//服务器的IP地址-本地主机
int ret = connect(sockfd,(struct sockaddr*)&srcaddr,sizeof(srcaddr));
if(ret == -1)
{
perror("connect failed");
return -1;
}
printf("connect OK\n");
return 0;
}
/*
*功能:验证登录或注册帐号信息
*输入:客户端套接字,登录/注册选择
*输出:登录成功1,注册成功2,任意失败-1
* */
int check_id(int sockfd,int choice)
{
char ID_info[17];//存储帐号信息以及登录/注册选择
int i;
char status[16];//存储登录/注册状态
int ret;
printf("check ID information!\n");
memset(ID_info,0,17);
//登录,携带ID 和密码
if(1 == choice){
ID_info[0]='1';
memcpy(ID_info+1,client_id.id,4);
memcpy(ID_info+9,client_id.passwd,8);
}
//注册,携带昵称和密码
else {
ID_info[0]='2';
memcpy(ID_info+5,client_id.name,4);
memcpy(ID_info+9,client_id.passwd,8);
}
//发送帐号信息给服务器端进行验证
for(i=0;i<16;i++){
if(ID_info[i] == '\0'){
ID_info[i] = '/';
}
}
ID_info[i] = '\0';
ret = send(sockfd, ID_info, strlen(ID_info), 0);
if(-1 == ret){
perror("send id_info error!");
return -1;
}
//接收帐号验证信息
memset(status,0,16);
ret = recv(sockfd,status,16,0);
if(-1 == ret){
perror("recv id_info error!");
return -1;
}
if(memcmp(status,"successfully!",13)==0){
//登录成功
//printf("login successfully!\n");
send(sockfd,"ok",3,0);
ret = recv(sockfd,client_id.name,4,0);
if(-1 == ret){
perror("recv ack_id_info error");
return -1;
}
return 1;
}
else if(memcmp(status,"sign up",7)==0){
//注册成功
//printf("sign up successfully!\n");
send(sockfd,"ok",3,0);
ret = recv(sockfd,client_id.id,4,0);
if(-1 == ret){
perror("recv ack_id_info error");
return -1;
}
return 2;
}
else
{
printf("login or sign up error!\n");
return -1;
}
}
/*
*功能:登录界面
*输入:客户端套接字
*输出:成功0,失败-1
*注意:登录失败可以重新登录,其他失败会退出
*/
int hwy_login(int sockfd )
{
int choice;//1--代表登录,2代表注册
int login_status;//登录状态,-1代表失败,1代表成功
int signup_status;//注册状态,-1代表失败,2代表成功
while(1){
printf("1------------login\n2------------sign up\n");
scanf("%d",&choice);
if(1 == choice){
//登录,输入ID和密码
printf("ID:");
scanf("%s",client_id.id);
printf("passwd:");
scanf("%s",client_id.passwd);
login_status=check_id(sockfd,choice);
if(1 == login_status){
//登录成功,进入聊天室
printf("欢迎登录聊天室~%s\n",client_id.name);
break;
}
else
continue;
}
else if(2 == choice){
//注册,输入昵称和密码
printf("name:");
scanf("%s",client_id.name);
printf("passwd:");
scanf("%s",client_id.passwd);
signup_status = check_id(sockfd,choice);
if(2 == signup_status){
//注册成功,返回登录界面
printf("注册成功\n");
printf("你的帐号为:%s\n请重新登录\n",client_id.id);
continue;
}
else {
//注册失败
return -1;
}
}
else {
printf("错误!请输入正确数值!\n");
continue;
}
}
return 0;
}
/*
*功能:获取聊天具体内容
*输入:保存聊天内容的指针
*输出:无
* */
//获取聊天具体内容
void get_send_content(char get_send_buffer[CHAT_STRUCT_SIZE])
{
int i;
char dest[4];//目标帐号
char time_buf[26];//时间
time_t t;
time(&t);
memset(get_send_buffer,0,CHAT_STRUCT_SIZE);
//发送者
memcpy(get_send_buffer+POSITION_SELFID,client_id.id,4);
memcpy(get_send_buffer+POSITION_NAME,client_id.name,4);
//接收者
//printf("你要发给谁?\n");
scanf("%s",get_send_buffer+POSITION_DESTID);
//发送内容
//printf("你要发什么?\n");
scanf("%s",get_send_buffer+POSITION_CONTENT);
//发送时间
memcpy(get_send_buffer+POSITION_TIME,ctime_r(&t,time_buf),26);
for(i=0;i<POSITION_CONTENT;i++){
if(get_send_buffer[i] == '\0')
get_send_buffer[i] = '/';
}
}
/*
*功能:处理接收信息的子线程处理函数
*
* */
void *pthread_recv_func (void *arg)
{
int sockfd = *(int *)arg;
int ret;
int i;
hwy_recv_msg_t hwy_msg;//谁发的,什么时候发,发了什么
char recv_buffer[CHAT_STRUCT_SIZE];//接收内容缓冲区
printf("现在可以聊天了~\n");
while(1){
memset(recv_buffer,0,CHAT_STRUCT_SIZE);
ret=recv(sockfd, recv_buffer,CHAT_STRUCT_SIZE, 0);
if(ret == -1){
printf("client received error!\n");
return;
}
else {
for(i=0;i<POSITION_CONTENT;i++){
if(recv_buffer[i]== '/')
recv_buffer[i]= '\0';
}
memcpy(hwy_msg.who,recv_buffer+POSITION_NAME,4);
memcpy(hwy_msg.time,recv_buffer+POSITION_TIME,26);
memcpy(hwy_msg.content,recv_buffer+POSITION_CONTENT,128);
printf("%s:%s\n%s",hwy_msg.who,hwy_msg.content,hwy_msg.time);
}
}
}
void *pthread_send_func (void *arg)
{
int sockfd = *(int *)arg;
char send_buffer[CHAT_STRUCT_SIZE];//发送内容缓冲区
while(1){
get_send_content(send_buffer);
//输入bye退出聊天室
if(memcmp(send_buffer,"bye",3)== 0 ){
send(sockfd,"byebye~", strlen(send_buffer), 0);
close(sockfd);
exit(0);
}
send(sockfd, send_buffer, strlen(send_buffer), 0);
}
}
int main(int argc,char *argv[])
{
int sockfd = sock_init();
if(sockfd == -1){
printf("sock_init error!\n");
return -1;
}
int ret = sock_client(sockfd);
if(-1 == ret){
printf("sock_client error!\n");
return -1;
}
ret = hwy_login(sockfd);
if(ret == -1){
perror("hwy_login function error!");
return -1;
}
//一个线程负责接收信息,一个线程负责发送信息
pthread_t tid1,tid2;
pthread_create(&tid2,0,pthread_recv_func,&sockfd);
pthread_create(&tid1,0,pthread_send_func,&sockfd);
while(1){
sleep(9);
}
close(sockfd);
return 0;
}
(3)server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "hwy_network_chat.h"
#include "sqlite3.h"
#include <stdlib.h>
//id--fd结构体类型
typedef struct
{
int client_fd;
char client_id[4];
}client_id_to_fd;
//将用户帐号和占用的文件描述符一一对应起来,
//方便后续一对一通信
client_id_to_fd id_to_fd[CLIENT_MAX];
//数据库的连接指针
static sqlite3 *db = NULL;
/*
*功能:建立网络通信的初始化工作,包括创建套接字和绑定可用端口号
*输入:无
*输出:成功返回套接字,失败返回-1
* */
int sock_init(void )
{
int ret;
//创建套接字--设置通信为IPv4的TCP通信
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket failed");
return -1;
}
//配置网络结构体
struct sockaddr_in myaddr;
myaddr.sin_port = htons(50000); //应用层端口号
myaddr.sin_family = AF_INET; //IPv4协议
myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//通信对象的IP地址,0.0.0.0表示所有IP地址
//绑定套接字和结构体----主机自检端口号是否重复,IP是否准确
ret = bind(sockfd,(struct sockaddr *)&myaddr,sizeof(myaddr));
if(ret == -1)
{
perror("bind failed");
return -1;
}
return sockfd;
}
/*
*功能:把服务器接收的信息发给所有人
*输入:聊天信息具体内容
*输出:无
* */
void SendMsgToAll(char *msg)
{
int i;
for(i=0;i<CLIENT_MAX;i++){
if(id_to_fd[i].client_fd != 0){
printf("sendto%s\n",id_to_fd[i].client_id);
printf("%s\n",msg);
send(id_to_fd[i].client_fd,msg,strlen(msg),0);
}
}
}
/*
* 功能:把服务器接收的消息发给指定的人
* 输入:目标帐号所绑定的fd,具体聊天内容
* 输出:无
*/
void SendMsgToSb(int destfd,char *msg)
{
int i;
for(i=0;i<CLIENT_MAX;i++){
if(id_to_fd[i].client_fd == destfd ){
printf("sendto%s\n",id_to_fd[i].client_id);
printf("%s\n",msg);
send(destfd,msg,strlen(msg),0);
break;
}
}
}
//数据库初始化工作
//连接数据库,创建表格
void hwyDataBase_init(void )
{
// 打开hwyhwy.db的数据库,如果数据库不存在,则创建并打开
sqlite3_open_v2("hwyhwy.db",&db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,//可读可写可创建
NULL);
if(db==NULL)
{
perror("sqlite3_open_v2 faield\n");
exit(-1);
}
//在数据库中创建表格
//ID 昵称 密码,ID为主键
char *errmsg = NULL;
char *sql = "CREATE TABLE if not exists hwy_id_sheet(id text primary key,\
name text not null,passwd text not null);";
sqlite3_exec(db, sql, NULL, NULL, &errmsg);
}
/*
*功能:验证客户端传来的ID信息
*输入:服务器accept后产生的套接字
*输出:验证状态,1登录成功,2注册成功,其他失败
* */
int check_recv_id(int fd )
{
int ret;
int i;
hwy_people_t client_ID_info; //帐号信息结构体ID_info
char recv_ID_info[17];//客户端传来的帐号信息其登录/注册选择
char ack_ID_info[4];
char ack_ok[3];
char *errmsg = NULL;
char sql[128];
int nrow=0;
int ncolumn=0;
char **azResult=NULL;
char status[16];
//接收ID_info
memset(recv_ID_info,0,17);
memset(ack_ID_info,0,4);
memset(sql,0,128);
memset(status,0,16);
ret = recv(fd, recv_ID_info, 17, 0);
if(-1 == ret){
perror("recv error!");
return -1;
}
//打印接收到的信息
for(i=0;i<17;i++){
if(recv_ID_info[i] == '/')
recv_ID_info[i] = '\0';
}
memcpy(client_ID_info.id,recv_ID_info+1,4);
memcpy(client_ID_info.name,recv_ID_info+5,4);
memcpy(client_ID_info.passwd,recv_ID_info+9,8);
//登录,验证输入的ID和passwd是否正确
if(recv_ID_info[0] == '1'){
sprintf(sql,"select * from hwy_id_sheet where id = '%s' and passwd = '%s';",
client_ID_info.id,client_ID_info.passwd);
sqlite3_get_table(db,sql,&azResult,&nrow,&ncolumn,&errmsg);
if(nrow == 0){
//没有匹配项,登录验证失败
strcpy(status,"login failed!");
send(fd,status,strlen(status),0);
return -1;
}
else {
//登录验证成功
memset(status,0,16);
strcpy(status,"successfully!");
send(fd,status,strlen(status),0);
recv(fd,ack_ok,3,0);
//在这里绑定client_fd---client_id
for(i=0;i<CLIENT_MAX;i++){
if(id_to_fd[i].client_fd == fd){
memcpy(id_to_fd[i].client_id,client_ID_info.id,4);
break;
}
}
//发送用户昵称
strcpy(ack_ID_info,azResult[4]);
send(fd,ack_ID_info,strlen(ack_ID_info),0);
return 1;
}
}
//注册,根据昵称和密码注册、记录帐号信息,并返回帐号
else {
int j = 100;
char *sql1 = "select * from hwy_id_sheet;";
sqlite3_get_table(db,sql1,&azResult,&nrow,&ncolumn,&errmsg);
j = j+ nrow;
memset(ack_ID_info,0,4);
sprintf(ack_ID_info,"%d",j);//---itoa
memcpy(client_ID_info.id,ack_ID_info,4);
sprintf(sql,"insert into hwy_id_sheet values('%s','%s','%s'); ",client_ID_info.id,
client_ID_info.name,client_ID_info.passwd);
ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
if(ret == SQLITE_OK){
printf("注册成功\n");
memset(status,0,16);
strcpy(status,"sign up");
send(fd,status,strlen(status),0);
recv(fd,ack_ok,3,0);
//发送用户帐号信息
send(fd,ack_ID_info,strlen(ack_ID_info),0);
return 2;
}
else {
printf("注册失败\n");
memset(status,0,16);
strcpy(status,"sign up error");
send(fd,status,strlen(status),0);
return -1;
}
}
}
//每接收一个客户端的连接,便创建一个线程
void * thread_func (void *arg)
{
int fd = *(int *)arg;
int ret;
int i;
hwy_send_msg_t hwy_C_SendMsg;
printf("pthread = %d\n",fd);
char recv_buffer[CHAT_STRUCT_SIZE];
//验证登录/注册信息
while(1){
ret = check_recv_id(fd);
printf("check_recv_id = %d\n",ret);
if(ret == 1){
//成功登录
printf("登录成功\n");
break;
}
else if(ret == 2){
//注册成功,需要重新登录
continue;
}
else {
//验证失败,服务器不退出
continue;
}
}
//登录成功,处理正常聊天的信息--接收与转发
while(1){
memset(recv_buffer,0,CHAT_STRUCT_SIZE);
ret = recv(fd, recv_buffer, CHAT_STRUCT_SIZE, 0);
if(-1 == ret){
perror("recv error!");
return;
}
else if(ret>0){
printf("接收到的内容为:%s\n",recv_buffer);
if(memcmp(recv_buffer+POSITION_DESTID,"999",3)== 0)
SendMsgToAll(recv_buffer);
else{
for(i = 0;i< CLIENT_MAX;i++){
if(memcmp(id_to_fd[i].client_id,recv_buffer+POSITION_DESTID,\
3)== 0){
SendMsgToSb(id_to_fd[i].client_fd,recv_buffer);
break;
}
}
}
}
}
}
void service(int sock_fd)
{
printf("服务器启动...\n");
listen(sock_fd,CLIENT_MAX);
while(1){
struct sockaddr_in clientaddr;
int len = sizeof(clientaddr);
int client_sock = accept(sock_fd,(struct sockaddr*)&clientaddr,&len);
if(client_sock== -1)
{
printf("accept failed...\n");
continue;
}
printf("accept OK!\n");
int i;
for(i=0;i<CLIENT_MAX;i++){
if(id_to_fd[i].client_fd == 0){
id_to_fd[i].client_fd = client_sock;
printf("client fd = %d\n",client_sock);
//有客户端连接之后,启动线程为此客户端服务
pthread_t tid;
pthread_create(&tid, 0,thread_func,&client_sock);
break;
}
}
if(i == CLIENT_MAX){
char * str = "对不起,聊天室已满人!\n";
send(client_sock,str,sizeof(str),0);
close(client_sock);
}
}
}
int main(int argc,char *argv[])
{
int sock_fd = sock_init();
hwyDataBase_init();
service(sock_fd);
return 0;
}
(3)数据库相关代码
sqlite3.c sqlite3.h这个可以下载。
(4)编译方法
gcc client.c -lpthread -o client
gcc server.c sqlite.c -ldl -lpthread -o server
(路过的还请赐教下这个怎么写Makefile??~)