基于TCP的服务器/客户机的模型
1.基本特征
面向连接的,可靠的,保证数据完整性和有序性
每个发送都有应答,若在时间窗口内没有收到A的应答,则从A开始重新发送。
编程模型
三次握手四次分手
服务器的实现
服务器的思路是等待客户端的连接,并且实时接收每个连接上来了的客户端发送的消息,并将这个消息包打包发送给其他客户端。
1.创建套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
//AF_INET: 基于TCP/IPv4(32位IP地址)的网络通信;
//SOCKET_STREAM:数据流协议,即TCP协议;
if(sfd == -1){
perror("socket");
return -1;
}
2.准备通信地址
struct sockaddr_in addr;
/*
struct sockaddr_in
19.{
20. // 地址族
21. sa_family_t sin_family;
22.
23. // 端口号
24. // unsigned short, 0-65535
25. // 逻辑上表示一个参与通信的进程
26. // 使用时需要转成网络字节序
27. // 0-1024端口一般被系统占用
28. // 如:21-FTP、23-Telnet、80-WWW
29. in_port_t sin_port;
30.
31. // IP地址
32. struct in_addr sin_addr;
33.};
*/
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
socketlen_t addrlen = sizeof(addr);
3.绑定套接字
int ret = bind(sfd,(const struct sockaddr*)(&addr),addrlen);
if(ret == -1){
perror("bind");
return -1;
}
4.监听套接字
将sockfd参数所标识的套接字标记为被动模式,使之可用于接受连接请求。
if(listen(sfd,10)==-1){
//10表示最大的客户端排队数为10
perror("listen");
return -1;
}
5.接受连接
自定义客户端类型
#define MAX 100
typedef struct Client{
int cfd;
char name[40];
}Client;
Client client[MAX] = {};
size_t cnt = 0;
对线程互斥锁初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
while(1){
struct sockaddr_in caddr;
socklen_t clen = sizeof(caddr);
printf("等待客户机连接...\n");
int cfd = accept(sfd,(struct sockaddr*)(&caddr),&clen);
/*从sockfd参数所标识套接字的未决连接请求队列中,提取第一个连接请求。
同时创建一个新的套接字,用于在该连接中通信,返回该套接字的描述符。
caddr和clen参数用于输出连接请求发起者的地址信息。
*/
if(cfd == -1){
perror("accept");
return -1;
}
//向其他客户端发送上线消息
char buf[100] = {};
//把客户端的网名和套接字放在客户端的类型中
recv(cfd,&client[cnt].name,40,0);
client[cnt].cfd = cfd;
strcpy(buf,"您的好友");
strcat(buf,client[cnt].name);
strcat(buf,"上线啦!");
//广播消息
broadcast(buf,client[cnt]);
pthread_t id;
ret = pthread_create(&id,NULL,pthread_run,(void*)(&client[cnt]));
//每当有一个客户机连接上来,变创建一个线程,该线程用于接收这个客户机的消息,并将这些消息加工广播给其他连接上来的客户机。
cnt++;
if(ret != 0){
printf("pthread_create:%s\n",strerror(ret));
continue;
}
printf("有一个客户机成功连接:ip <%s> port [%hu]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
}
6.线程处理函数
实现消息的接收处理转发功能
void *pthread_run(void *arg){
Client cl = *(Client*)(arg);
while(1){
char buf[1024] = {};
strcpy(buf,cl.name);
strcat(buf," :");
int ret = recv(cl.cfd,buf+strlen(buf),1024-strlen(buf),0);
//j接收消息
if(ret <= 0 ){
size_t i;
for(i=0;i<cnt;i++){
if(client[i].cfd == cl.cfd){
client[i] = client[cnt-1];
cnt--;
strcpy(buf,"您的好友");
strcat(buf,cl.name);
strcat(buf,"退出了!");
break;
}
}
broadcast(buf,cl);
return NULL;
}else{
broadcast(buf,cl);
}
}
}
7.广播函数
void broadcast(char *msg,Client c){
size_t i;
pthread_mutex_lock(&mutex);
for(i=0;i<cnt;i++){
if(client[i].cfd != c.cfd){
if(send(client[i].cfd,msg,strlen(msg),0)<=0){
break;
}
}
}
pthread_mutex_unlock(&mutex);
}
8.服务器源码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAX 100
typedef struct Client{
int cfd;
char name[40];
}Client;
Client client[MAX] = {};
size_t cnt = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void broadcast(char *msg,Client c){
size_t i;
pthread_mutex_lock(&mutex);
for(i=0;i<cnt;i++){
if(client[i].cfd != c.cfd){
if(send(client[i].cfd,msg,strlen(msg),0)<=0){
break;
}
}
}
pthread_mutex_unlock(&mutex);
}
void *pthread_run(void *arg){
Client cl = *(Client*)(arg);
while(1){
char buf[1024]={};
strcpy(buf,cl.name);
strcat(buf," :");
int ret = recv(cl.cfd,buf+strlen(buf),1024-strlen(buf),0);
if(ret <= 0){
size_t i;
for(i=0;i<cnt;i++){
if(client[i].cfd == cl.cfd){
client[i] = client[cnt-1];
--cnt;
strcpy(buf,"您的好友");
strcat(buf,cl.name);
strcat(buf,"退出了");
break;
}
}
broadcast(buf,cl);
close(cl.cfd);
return NULL;
}else{
broadcast(buf,cl);
}
}
}
int main(int argc,char *argv[]){
if(argc != 3){
fprintf(stderr,"use: %s <ip> [port]\n",argv[0]);
return -1;
}
const char *ip = argv[1];
unsigned short int port = atoi(argv[2]);
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd == -1){
perror("socket");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
socklen_t addrlen = sizeof(addr);
int ret = bind(sfd,(struct sockaddr*)(&addr),addrlen);
if(ret == -1){
perror("bind");
return -1;
}
if(listen(sfd,10)==-1){
perror("listen");
return -1;
}
while(1){
struct sockaddr_in caddr;
socklen_t len = sizeof(caddr);
printf("等待客户机连接....\n");
int cfd = accept(sfd,(struct sockaddr*)(&caddr),&len);
if(cfd == -1){
perror("accept");
return -1;
}
char buf[100]={};
recv(cfd,&client[cnt].name,40,0);
client[cnt].cfd = cfd;
pthread_t id;
strcpy(buf,"您的好友");
strcat(buf,client[cnt].name);
strcat(buf,"上线啦");
broadcast(buf,client[cnt]);
ret = pthread_create(&id,NULL,pthread_run,(void*)(&client[cnt]));
cnt++;
if(ret != 0){
printf("pthread_create:%s\n",strerror(ret));
continue;
}
printf("有一个客户机成功连接:ip <%s> port [%hu]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
}
return 0;
}
客户机的实现
1.准备套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd == -1){
perror("socket");
return -1;
}
2.准备通信地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
socklen_t addrlen = sizeof(addr);
3.连接服务器
int ret = connect(sfd,(const struct sockaddr*)(&addr),addrlen);
if(ret == -1){
perror("connect");
return -1;
}
4.创建一个子进程接发消息
创建一个子进程,总共两个进程,其中一个进程用来接收客户端发送过来的消息,另外一个进程用于向服务器发送消息。
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){ //子进程,用于发送消息
while(1){
char buf[1024]={};
gets(buf);
if(send(sfd,buf,strlen(buf)+1,0)<=0){
break;
}
}
}else{ //父进程,用于接收消息
while(1){
char buf[1024]={};
if(recv(sfd,buf,102410)<=0){
break;
}
time_t timep;
time(&timep);
printf("%s\n",ctime(&timep)); //显示接收消息时间
printf("%s\n",buf);
}
}
5.客户机源码
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc,char *argv[]){
if(argc != 3){
fprintf(stderr,"use: %s <ip> [port]\n",argv[0]);
return -1;
}
const char *ip = argv[1];
unsigned short int port = atoi(argv[2]);
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd == -1){
perror("socket");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
socklen_t addrlen = sizeof(addr);
int ret = connect(sfd,(const struct sockaddr*)(&addr),addrlen);
if(ret == -1){
perror("connect");
return -1;
}
printf("连接服务器成功!\n");
printf("请输入你的网民:");
char name[100];
gets(name);
send(sfd,name,strlen(name)+1,0);
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
while(1){
char buf[1024]={};
gets(buf);
if(send(sfd,buf,strlen(buf)+1,0)<=0){
break;
}
}
}else{
while(1){
char buf[1024]={};
if(recv(sfd,buf,1024,0)<=0){
break;
}
time_t timep;
time(&timep);
printf("%s\n",ctime(&timep));
printf("%s\n",buf);
}
}
close(sfd);
return 0;
}