首次接触socket编程,通过在linux下实现一个简单聊天程序的客户端和服务端,加深对socket和linux的一些了解.
本篇先实现服务端的一些功能.
首先创建socket,在服务端socket需要绑定本地端口,对其进行相应初始化
本篇先实现服务端的一些功能.
首先创建socket,在服务端socket需要绑定本地端口,对其进行相应初始化
///定义sockfd
///流式Socket(SOCK_STREAM)针对于面向连接的TCP
///数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP
int server_create = socket(AF_INET,SOCK_STREAM, 0);
if(server_create == -1)
{
perror("socket create false");
exit(1);
}
///定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(SERVERPORT);
server_sockaddr.sin_addr.s_addr = inet_addr(SERVERIP);
socket结构体的相应细节许多人之前也都已经提到过,这里不再赘述.
socket在创建完成后,需要进行绑定
///bind,成功返回0,出错返回-1
///struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式,
///二者长度一样,都是16个字节.二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr,
///一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中.
if(bind(server_create,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
perror("bind");
exit(1);
}
接下来将socket设置为监听
///sten,成功返回0,出错返回-1
///socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,
///等待客户的连接请求.
if(listen(server_create,QUEUE) == -1)
{
perror("listen");
exit(1);
}
至此socket就可以监听其他socket所发送的connect请求了,但是由于服务端需要同时处理多个用户的请求,所以此处使用多线程进行相应处理.
思路大概为本socket每监听到一个请求,便创建一个新的线程来处理.
使用一个while来实现
mi = 0;
while(1)
{
struct conMySocket my_consok;
socklen_t length = sizeof(my_consok.clientSocket);
///成功返回非负描述字,出错返回-1
///accept在此处为阻塞函数,提取出所监听套接字的等待连接队列中第一个连接请求
my_consok.con = accept(server_create, (struct sockaddr*)&(my_consok.clientSocket), &length);
if(my_consok.con < 0)
{
perror("connect");
exit(1);
}
if(pthread_create(&thrid[mi], NULL, (void *)client_mythrdeal, &my_consok))
{
printf ("Create pthread error!\n");
exit(1);
}
mi++;
}
服务端的主要作用是为接入的用户提供其他用户的IP及端口号等相应信息,用户同用户的交流由客户端实现.
至此服务端主线程已经实现得差不多了,接下来是监听后创建的单独处理的线程所要实现的功能.
int client_mythrdeal(void* pmy_consok)
{
char *ip = NULL;
struct conMySocket myConSok = *(struct conMySocket*)pmy_consok;
struct user_info nowUserInfo;
memset(&nowUserInfo, 0, sizeof(struct user_info));
//inet_ntoa要求的参数只需要到sin_addr,而sin_addr下的s_addr不需要
strcpy(nowUserInfo.user_ipadrs, inet_ntoa(myConSok.clientSocket.sin_addr));
sprintf(nowUserInfo.user_port, "%d", ntohs(myConSok.clientSocket.sin_port));
ip = inet_ntoa(myConSok.clientSocket.sin_addr);
printf("IP:%s:%d\n", ip, myConSok.clientSocket.sin_port);
int nowId = 0;
while(1)
{
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE * sizeof(char));
recv(myConSok.con, buffer, BUFFER_SIZE * sizeof(char), 0);
printf("%s\n", buffer);
//若为测试连接标志,发送确定标志返回
if(strcmp(buffer, TESTSIGN) == 0)
{
send(myConSok.con, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
continue;
}
//若为发送端口标志,发送确定标志并准备接收端口号并对nowUserInfo.user_serverport赋值
if(strcmp(buffer, SENDPORTSIGN) == 0)
{
send(myConSok.con, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
memset(buffer, 0, BUFFER_SIZE * sizeof(char));
recv(myConSok.con, buffer, BUFFER_SIZE * sizeof(char), 0);
strcpy(nowUserInfo.user_serverport, buffer);
}
//若为登陆核对标志,则调用账户密码核对函数
if(strcmp(buffer, CHECKSIGN) == 0)
{
nowId = checkAccountAndPassword(myConSok.con, nowUserInfo.user_ipadrs, nowUserInfo.user_port, nowUserInfo.user_serverport);
continue;
}
//若为请求在线用户信息标志,则调用在线用户信息反馈函数
if(strcmp(buffer, ONLINEUSERINFOREQUEST) == 0)
{
onlineUserInfoRet(myConSok.con, nowId);
continue;
}
//若为请求其他用户IP和绑定的端口号,则调用tagUserIPAndPort函数
if(strcmp(buffer, OTHERUSERSOCKETREQUEST) == 0)
{
tagUserIPAndPort(myConSok.con);
continue;
}
}
close(myConSok.con);
return 0;
}
接下来是相应的功能函数实现
//账户密码核对
int checkAccountAndPassword(int requestSocket, char *user_ipadrs, char *user_port, char *user_serverport)
{
char userAccount[SHORT_STRING], userPassword[SHORT_STRING];
memset(userAccount, 0, SHORT_STRING * sizeof(char));
memset(userPassword, 0, SHORT_STRING * sizeof(char));
send(requestSocket, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
recv(requestSocket, userAccount, SHORT_STRING * sizeof(char), 0);
recv(requestSocket, userPassword, SHORT_STRING * sizeof(char), 0);
int i = 0;//计数用
while(userlist[i] != NULL)
{
if((strcmp((*userlist[i]).user_account, userAccount) == 0) && (strcmp((*userlist[i]).user_password, userPassword) == 0))
{
//若账户密码核对正确,则将用户当前客户端所具信息存储到用户信息表中,并将设为用户设为在线状态
strcpy((*userlist[i]).user_ipadrs, user_ipadrs);
strcpy((*userlist[i]).user_port, user_port);
strcpy((*userlist[i]).user_serverport, user_serverport);
(*userlist[i]).yn_online = 1;
send(requestSocket, LOGINSUCCESSRET, sizeof(LOGINSUCCESSRET), 0);
return i;
}
i++;
}
send(requestSocket, LOGINFAILURERET, sizeof(LOGINFAILURERET), 0);
return 0;
}
//在线用户信息反馈
int onlineUserInfoRet(int requestSocket, int selfId)
{
int i = 0;
char onlineUserInfoStr[MAXINFOSTRLENGTH];
memset(onlineUserInfoStr, 0, MAXINFOSTRLENGTH * sizeof(char));
send(requestSocket, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
while(userlist[i] != NULL)
{
if((*userlist[i]).yn_online == 1 && i != selfId)
{
char id[10];
memset(id, 0, 10 * sizeof(char));
sprintf(id, "%d", (*userlist[i]).id_num);
strcat(onlineUserInfoStr, " ");
strcat(onlineUserInfoStr, id);
}
i++;
}
if(strlen(onlineUserInfoStr))
send(requestSocket, onlineUserInfoStr, sizeof(onlineUserInfoStr), 0);
else
send(requestSocket, NOBODYONLIEN, sizeof(NOBODYONLIEN), 0);
}
//将其他用户IP和端口号反馈给请求用户
int tagUserIPAndPort(int requestSocket)
{
int i = 0;
char tagUserIdStr[SHORT_STRING];
memset(tagUserIdStr, 0, SHORT_STRING * sizeof(char));
send(requestSocket, TESTSIGNSUCS, sizeof(TESTSIGNSUCS), 0);
recv(requestSocket, tagUserIdStr, SHORT_STRING * sizeof(char), 0);
while(userlist[i] != NULL)
{
if((*userlist[i]).id_num == atoi(tagUserIdStr))
{
//发送用户存在标志后,继续发送用户IP和用户所在客户端的服务端绑定的端口号
send(requestSocket, USEREXIST, sizeof(USEREXIST), 0);
send(requestSocket, (*userlist[i]).user_ipadrs, SHORT_STRING * sizeof(char), 0);
send(requestSocket, (*userlist[i]).user_serverport, SHORT_STRING * sizeof(char), 0);
return 0;
}
i++;
}
send(requestSocket, USERNOEXIST, sizeof(USERNOEXIST), 0);
return 1;
}
服务端的主要代码至此已经编写的差不多了,接下来总结一下需要注意的细节吧.
首先是pthread的线程函数的参数传递,若要传递多个参数,可先创建一个结构体,然后将结构体的指针作为线程函数的参数传递进去.
另外在gcc下貌似是没有itoa函数的,可以用sprintf来代替.
由于用到了pthread,在编译的时候需加上-pthread
服务端的总结大概就到这吧,接下来是客户端的实现.