本课程设计是在Linux环境下基于Socket进行开发的。因为之前也学过计算机网络原理,接触过TCP/UDP 这些东西,所以就想试着写一个聊天室的功能出来。通过之前的学习,我知道了系统服务器和客户瑞组成。服务端程序通过共享存储区存储聊天数据,并发送给每个连接的客产端。通过多路复用的子进程实现服务端与多个客户端之间的数据发送与接收。可以在单机上开将两个该口分别运行客户、服务器的程序。经linux下gcc调试成功,可以实现简单的群聊效果。关键词:网络聊天,linux ,socket.相关概念及技术
实现语言:C语言
VmWare+Centos7.5
#include
#include
#include
#include
#include
#include
#include
#include
#include
int clientfd2;//客户端socket
char* IP = "127.0.0.1";//服务器的IP
short PORT = 6666;//服务器服务端口
typedef struct sockaddr meng;
char name[30];//设置支持的用户名长度
time_t nowtime;
void init(){
clientfd2 = socket(PF_INET,SOCK_STREAM,0);//创建套接字
struct sockaddr_in addr;//将套接字存在sockaddr_in结构体中
addr.sin_family = PF_INET;//地址族
addr.sin_port = htons(PORT);//端口号 可随意设置,不过不可超过规定的范围
addr.sin_addr.s_addr = inet_addr(IP);//inet_addr()函数将点分十进制的字符串转换为32位的网络字节顺序的ip信息
//发起连接
if (connect(clientfd2,(meng*)&addr,sizeof(addr)) == -1){
perror("无法连接到服务器");
exit(-1);
}
printf("客户端启动成功\n");
}
void start(){
pthread_t id;
void* recv_thread(void*);
//创建一个线程用于数据的接收,一个用于数据的发送
pthread_create(&id,0,recv_thread,0);
char buf2[100] = {};
sprintf(buf2,"%s进入了群聊",name);
time(&nowtime);
printf("进入的时间是: %s\n",ctime(&nowtime));
send(clientfd2,buf2,strlen(buf2),0);
while(1){
char buf[100] = {};
scanf("%s",buf);
char msg[100] = {};
sprintf(msg,"%s发送的信息是:%s",name,buf);
send(clientfd2,msg,strlen(msg),0);
if (strcmp(buf,"quit") == 0){
memset(buf2,0,sizeof(buf2));//初始化
sprintf(buf2,"%s退出了群聊",name);
send(clientfd2,buf2,strlen(buf2),0);
break;
}
}
close(clientfd2);
}
void* recv_thread(void* p){
while(1){
char buf[100] = {};
if (recv(clientfd2,buf,sizeof(buf),0) <= 0){
break;
}
printf("%s\n",buf);
}
}
int main(){
init();
printf("请输入用户名:");
scanf("%s",name);
printf("\n\n*****************************\n");
printf("欢迎%s 进入群聊\n",name);
printf(" 输入quit 退出\n");
printf("\n*****************************\n\n");
start();
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
int serverfd;//服务器socket
int clientfd[100];//客户端的socketfd,100个元素,clientfd[0]~clientfd[99]
int size =50;//用来控制进入聊天室的人数为50以内
char* IP = "127.0.0.1";//主机ip地址
short PORT = 6666;//端口号
typedef struct sockaddr meng;
time_t nowtime;
void init(){
serverfd = socket(PF_INET,SOCK_STREAM,0);
if (serverfd == -1){
perror("创建socket失败");
exit(-1);
}
//为套接字设置ip协议 设置端口号 并自动获取本机ip转化为网络ip
struct sockaddr_in addr;//存储套接字的信息
addr.sin_family = PF_INET;//地址族
addr.sin_port = htons(PORT);//设置server端端口号,你可以随便设置,当sin_port = 0时,系统随机选择一个未被使用的端口号
addr.sin_addr.s_addr = inet_addr(IP);//把127.0.0.1改为自己的server端的ip地址,当sin_addr = INADDR_ANY时,表示从本机的任一网卡接收数据
//绑定套接字
if (bind(serverfd,(meng*)&addr,sizeof(addr)) == -1){
perror("绑定失败");
exit(-1);
}
if (listen(serverfd,100) == -1){//监听最大连接数
perror("设置监听失败");
exit(-1);
}
}
void SendAll(char* msg){
int i;
for (i = 0;i < size;i++){
if (clientfd[i] != 0){
printf("发送给%d\n",clientfd[i]);
printf("发送的信息是: %s\n",msg);
//写入文件
char buf[1024];
FILE *logs = fopen("log.txt", "a+");
if(logs== NULL)
{
printf("open file erroe: \n");
}else{
sprintf(buf, "进入时间:%s\tIP地址:%s\n",ctime(&nowtime),IP);
fputs(buf,logs);
sprintf(buf, "所发信息:%s\n",msg);
fputs(buf,logs);
fclose(logs);
}
send(clientfd[i],msg,strlen(msg),0);
}
}
}
void* server_thread(void* p){
int fd = *(int*)p;
printf("pthread = %d\n",fd);
while(1){
char buf[100] = {};
if (recv(fd,buf,sizeof(buf),0) <= 0){
int i;
for (i = 0;i < size;i++){
if (fd == clientfd[i]){
clientfd[i] = 0;
break;
}
}
printf("退出:fd = %d 退出了。\n",fd);
char buf[1024];
FILE *logs = fopen("log.txt", "a");
if(logs== NULL)
{
printf("open file erroe: \n");
}else{
sprintf(buf, "退出时间:%s\tIP地址:%s\n",ctime(&nowtime),IP);
fputs(buf,logs);
fclose(logs);
}
pthread_exit(0);
}
//把服务器接受到的信息发给所有的客户端
SendAll(buf);
}
}
void server(){
printf("服务器启动\n");
while(1){
struct sockaddr_in fromaddr;
socklen_t len = sizeof(fromaddr);
int fd = accept(serverfd,(meng*)&fromaddr,&len);
//调用accept进入堵塞状态,等待客户端的连接
if (fd == -1){
printf("客户端连接出错...\n");
continue;
}
int i = 0;
for (i = 0;i < size;i++){
if (clientfd[i] == 0){
//记录客户端的socket
clientfd[i] = fd;
printf("线程号= %d\n",fd);//
//有客户端连接之后,启动线程给此客户服务
pthread_t tid;
pthread_create(&tid,0,server_thread,&fd);
break;
}
if (size == i){
//发送给客户端说聊天室满了
char* str = "对不起,聊天室已经满了!";
send(fd,str,strlen(str),0);
close(fd);
}
}
}
}
int main(){
init();
server();
}
客户端的过程比较简单,创建 Socket,连接服务器,将 Socket 与远程主机连接(注意:只有 TCP 才有“连接”的概念,一些 Socket 比如 UDP、ICMP 和 ARP 没有“连接”的概念),发送数据,读取响应数据,直到数据交换完毕,关闭连接,结束 TCP 对话。
accept()
阻塞,等待来自客户端的连接。如果这时客户端与服务器建立了连接,客户端发送数据请求,服务器接收请求并处理请求,然后把响应数据发送给客户端,客户端读取数据,直到数据交换完毕。最后关闭连接,交互结束。accept()
时,Socket 会进入waiting状态。客户端请求连接时,方法建立连接并返回服务器。accept()
返回一个含有两个元素的元组 (conn, addr)。第一个元素 conn 是新的 Socket 对象,服务器必须通过它与客户端通信;第二个元素 addr 是客户端的 IP 地址及端口。send()
和 recv()
通信(传输数据)。send()
,并采用字符串形式向客户端发送信息,send()
返回已发送的字符个数。recv()
从客户端接收信息。调用 recv()
时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。recv()
在接收数据时会进入blocked状态,最后返回一个字符串,用它表示收到的数据。如果发送的数据量超过了 recv()
所允许的,数据会被截短。多余的数据将缓冲于接收端,以后调用 recv()
时,会继续读剩余的字节,如果有多余的数据会从缓冲区删除(以及自上次调用 recv()
以来,客户端可能发送的其它任何数据)。传输结束,服务器调用 Socket 的 close()
关闭连接。TCP 三次握手的 Socket 过程:
socket()
、bind()
、listen()
完成初始化后,调用 accept()
阻塞等待;connect()
向服务器发送了一个 SYN 并阻塞;connect()
返回,再发送一个 ACK 给服务器;accept()
返回,建立连接5.TCP 四次挥手的 Socket 过程:
close()
主动关闭,发送一个 FIN;close()
关闭 Socket,并也发送一个 FIN;5.运行截图
图 1 启动服务端
图 2 服务端成功启动
图 3 启动客户端
图 4 客户端输入用户名
图 5 启动两个客户端
图 6 群聊实现
注:上面的代码如果直接拷入可能无法使用(字符编码问题)
借鉴(Socket 通信原理):https://segmentfault.com/a/1190000013712747