功能介绍:

  此demo是基于TCP套接字编程,目的是实现一个聊天室效果。类似于QQ群效果,如果上线可以通知其他好友,下线也会通知其他好友。

需要用的技术:

  一、socket编程。

    1> socket 网络编程常识:既要考虑客户端 又要考虑服务器端。

    2>TCP 一对多开发步骤:

    服务端:

      ①:创建socket,使用socket()    

#include <sys/socket.h>
int socket(int family, int type, int protocol);

        family: 指明协议族

AF_INET: IPv4协议;

AF_INET6: IPv6协议;

AF_LOCAL:  Unix域协议;

AF_ROUTE: 路由套接字;

AF_KEY: 密钥套接字;

type:指明套接字类型

SOCK_STREAM   字节流套接字(TCP)

SOCK_DGRAM    数据报套接字(UDP)

SOCK_SEQPACKET  有序分组套接字

SOCK_RAW        原始套接字

protocol:设为某个协议类型常值  一般设为 0;

      ② 准备通信地址,sockaddr_in:

struct sockaddr{
sa_family_t sa_family;
char sa_data[];
....
....
}; struct in_addr{
in_addr_t s_addr;
}; struct sockaddr_in{
sa_family_t sin_family; //协议族 要和socket中的family相同
in_port_t sin_port; //网络端口
struct in_addr sin_addr; //网络地址
};

需要注意的是在对sockaddr_in 赋值的时候需要用到两个函数:

#include <arpa/inet.h>
uint16_t htons(uint16_t hostint16); //转换成网络字节序表示的16位整型数 in_addr_t inet_addr(const char *cp); //把字符串转换成32位二进制网络字节序的IPV4地址

      ③:绑定 套接字描述符 和 通信地址 使用函数 bind();

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

需要注意的是为使不同格式地址能够被传入到套接字函数,地址被强制转换成通用的地址结构 sockadd表示。

      ④:监听客户端, 使用函数 listen();

#include <sys/socket.h>
int listen(int sockfd, int backlog);

      ⑤:等待客户端的连接,使用函数 accept(). 此函数在客户端连接上来后,将返回一个新的socket描述符,这个心得描述符用于和客户端的交互。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

        使用此函数需要注意的是,如果想从客户端那里得到一些信息需要重新声明一个 struct socketaddr_in 类型的参数,然后传给此函数的第二个和第三个参数即可。

      ⑥:把accept 返回的socket描述符当做文件描述符来操作即可;

#include <unistd.h>

  ssize_t read(int filedes, void *buf, size_t nbytes);
ssize_t write(int filedes, const void *buf, size_t nbytes); ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);

        recv 的返回值: >0表示接受到消息的字节数; =0表示对方已经断开连接; <0 表示出错;

      ⑦:关闭描述符;

    客户端:

      ①:创建socket,使用socket()

      ②: 准备通信地址,sockaddr_in:

      ③:建立连接 使用函数 connect()

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);

      ④:把socketfd当做文件描述符来使用即可,就像服务端的第六步。

  二、多线程技术;

编程思想:

  一、首先需要两个执行程序,一个模拟客户端,一个模拟服务端

  二、客户端首先使用socket connect 等函数连接上服务器,然后创建多线程用来接收服务器发来的消息,主线程用来发送消息到服务器。

  三、服务器端:

    ①:首先创建一个全局变量数组用来保存客户端连接上来的信息,因为不止一个客户端连接上。

    ②:创建 socket 套接字,然后监听客户端发来的消息,当有客户端连接上来的时候创建多线程为此客户端服务。

需要注意的是对全局变量——保存所有客户端套接字的数组。当客户端断开连接的时候需要清理数组中的信息。

client.c:

 #include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
//准备工作
int sockfd;//
char* IP = "127.0.0.1";//本机IP,回送地址
short PORT = ;
typedef struct sockaddr SA;//类型转换
char name[];//客户端昵称
//启动客户端,连接服务器
void init(){
printf("聊天室客户端开始启动\n");
sockfd = socket(AF_INET,SOCK_STREAM,);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
if(connect(sockfd,(SA*)&addr,sizeof(addr))==-){
perror("无法连接到服务器");
printf("客户端启动失败\n");
exit(-);
}
printf("客户端启动成功\n");
}
//开始通信
void* recv_thread(void* p){//收消息
while(){
char buf[] = {};
if(recv(sockfd,buf,sizeof(buf),)<=){
return;
}
printf("%s\n",buf);
}
}
void start(){
//发送消息
//发消息之前,启动一个线程,用来接受服务器发送过来的消息
pthread_t pid;
pthread_create(&pid,,recv_thread,);
while(){
char buf[] = {};
scanf("%s",buf);//接受用户输入
char msg[] = {};
sprintf(msg,"%s 说:%s",name,buf);
send(sockfd,msg,strlen(msg),);//发给服务器
}
}
void sig_close(){
//关闭客户端的描述符
close(sockfd);
exit();
}
int main(){
signal(SIGINT,sig_close);//关闭CTRL+C
printf("请输入您的昵称:");
scanf("%s",name);
init();//连接服务器
send(sockfd,name,strlen(name),);//将昵称发给服务器
start();//开始通信
return ;
}

server.c:

 #include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
//准备工作
int sockfd;//
char* IP = "127.0.0.1";//本机IP,回送地址
short PORT = ;
typedef struct sockaddr SA;//类型转换
struct client{//
char name[];//存储客户昵称
int fds;//客户端socket描述符
};
struct client c[] = {};//最多记录100个链接到服务器的客户端
int size = ;//记录客户端的个数,数组的索引
//初始化服务器的网络,创建socket
void init(){
printf("聊天室服务器开始启动..\n");
sockfd = socket(AF_INET,SOCK_STREAM,);
if(sockfd == -){
perror("创建socket失败");
printf("服务器启动失败\n");
exit(-);
}
//准备网络通信地址
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
if(bind(sockfd,(SA*)&addr,sizeof(addr))==-){
perror("绑定失败");
printf("服务器启动失败\n");
exit(-);
}
printf("成功绑定\n");
//设置监听
if(listen(sockfd,)==-){
perror("设置监听失败");
printf("服务器启动失败\n");
exit(-);
}
printf("设置监听成功\n");
printf("初始化服务器成功\n");
//等待客户端链接,放到另一个函数中
}
//线程函数,用来接受客户端的消息,并把消息发给所有客户端
//分发消息函数
void sendMsgToAll(char* msg){
int i = ;
for(;i<size;i++){
printf("sendto%d\n",c[i].fds);
send(c[i].fds,msg,strlen(msg),);
}
}
void* service_thread(void* p){
int fd = *(int*)p;//拿到标记客户端的sockfd
printf("pthread=%d\n",fd);//输出测试
//记录客户端的sockfd
c[size].fds = fd;
char name[] = {};
if(recv(fd,name,sizeof(name),)>){
strcpy(c[size].name,name);//拿到昵称
}
size++;
char tishi[] = {};
//群发通知消息
sprintf(tishi,"热烈欢迎 %s 登录聊天室..",name);
//发给所有人
sendMsgToAll(tishi);
while(){
char buf[] = {};
if(recv(fd,buf,sizeof(buf),)==){
//返回0,表示TCP另一端断开链接
//有客户端退出
printf("fd=%dquit\n",fd);//测试
int i;
char name[] = {};
for(i=;i<size;i++){
if(c[i].fds == fd){
strcpy(name,c[i].name);
c[i].fds = c[size-].fds;
strcpy(c[i].name,c[size-].name);//用最后一个有效的数组元素,覆盖当前没用的这个保存退出的客户端的信息的数组元素
}
}
size--;
printf("quit->fd=%dquit\n",fd);
char msg[] = {};
sprintf(msg,"欢送 %s 离开聊天室,再见!",name);
//群发退出通知
sendMsgToAll(msg);
close(fd);
return;//客户端退出了,结束服务线程
}
sendMsgToAll(buf);//接受成功,广播聊天信息
}
}
//等待客户端的连接,启动服务器的服务
void service(){
printf("服务器开始服务\n");
while(){
struct sockaddr_in fromaddr;
socklen_t len = sizeof(fromaddr);
int fd = accept(sockfd,(SA*)&fromaddr,&len);
if(fd == -){
printf("客户端链接出错\n");
continue;//继续循环,处理连接
}
//如果客户端成功连接上
printf("fd=%d\n",fd);//测试
//启动线程
pthread_t pid;
pthread_create(&pid,,service_thread,&fd);
}
}
void sig_close(){
//关闭服务器的socket
close(sockfd);
printf("服务器已经关闭\n");
exit();
}
int main(){
signal(SIGINT,sig_close);//退出CTRL+C
init();
service();
return ;
}

  以上的代码是老师写的,老师用的是数组来保存所有的客户端的信息。但是自己在做这个程序的时候想法有点偏差,我用的是动态分配的链表技术来保存连接上来得客户端的socket 套接字,动态分配,动态删除。代码如下:

client.c:

 #include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h> struct info{
char name[];
char buf[];
}; //利用多线程接收发来的信息,可以在任何时候接收信息,包括发信息的时候
void* getmssage(void* p){
int* sockfd = (int*)p;
struct info from;
while(recv(*sockfd,&from,sizeof(from),) > ){
printf("%s说:%s\n",from.name,from.buf);
memset(from.name,,sizeof(from.name));
memset(from.buf,,sizeof(from.buf));
}
} int main(){
int sockfd = socket(AF_INET,SOCK_STREAM,);
if(sockfd == -) perror("socket"),exit(-);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("172.30.13.70"); int res = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
if(res == -) perror("connect"),exit(-); struct info in;
printf("请输入用户名:");
scanf("%s",in.name);
getchar(); //此时必须要用getchar 因为第 43 行用的是gets接收数据, 38行的scanf函数接收数据的时候碰到 \n就结束了,下面的gets 就会把缓存区的 \n 给接收到, 如果下面用 scanf 接收数据的话,此时就不需要用 getchar了, 因为 scanf 在第一个字符碰到 \n的时候会直接把 \n 给忽略掉
pthread_t pid;
pthread_create(pid,,getmssage,(void*)&sockfd);
while(){
//scanf("%s",in.buf);
gets(in.buf);
if(!strcmp(in.buf,"bye")){
strcpy(in.buf,"退出登陆");
send(sockfd,&in,sizeof(in),);
exit();
}
if(send(sockfd,&in,sizeof(in),) == -) perror("send"),exit(-);
memset(in.buf,,strlen(in.buf));
}
}

server.c:

 #include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/types.h>
/*
int accfd[10];
static int i = 0;
int pthid[10];
*/
struct info{
int accfd;
int pthid;
int num;
struct info* next;
}; struct clinfo{
char name[];
char buf[];
}; static int i = ;
struct info* head; void* del(int n){
struct info *p;
struct info *t; t = head;
p = head->next;
printf("del n:%d\n",n);
while(p){
if(p->num == n){
t->next = p->next;
free(p);
break;
}
p = p->next;
t = t->next;
} printf("after free\n");
t = head->next;
while(t){
printf("t->num:%d\n",t->num);
t = t->next;
}
} void* message(void* p){
printf("this is message\n");
int num = (int)p;
struct info* t = head;
t = t->next; int accfd;
int pthid; while(t){
if(t->num == num){
accfd = t->accfd;
pthid = t->pthid;
break;
}
t = t->next;
} struct clinfo cin;
memset(cin.buf,,sizeof(cin.buf));
memset(cin.name,,sizeof(cin.name));
while(){
while(recv(accfd,&cin,sizeof(cin),) > ){
printf("%s say: %s\n",cin.name,cin.buf);
t = head->next;
while(t){
printf("t->num:%d,num:%d\n",t->num,num);
if(t->num != num){
send(t->accfd,&cin,sizeof(cin),);
}
t = t->next;
}
if(!strcmp(cin.buf,"退出登陆")){
del(num);
printf("即将退出线程\n");
close(accfd);
sleep();
pthread_exit();
}
memset(cin.buf,,sizeof(cin.buf));
memset(cin.name,,sizeof(cin.name));
}
}
} struct info* init(){
struct info* in;
in = (struct info*)malloc(sizeof(struct info));
in->next = NULL;
return in;
} int main(){
int sockfd = socket(AF_INET,SOCK_STREAM,);
if(sockfd == -) perror("socket"),exit(-);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons();
addr.sin_addr.s_addr = inet_addr("172.30.13.70"); //解决地址被占用问题
int reuse = ;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
//绑定
int res = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));
if(res == -) perror("bind"),exit(-);
//监听
listen(sockfd,);
struct sockaddr_in from;
socklen_t len = sizeof(from); head = init();
while(){
//得到新的描述符
int accfd = accept(sockfd,(struct sockaddr*)&from,&len);
struct info* p = head;
struct info* tmp = init();
//让指针指向链表的尾部
while(p->next){
p = p->next;
}
p->next = tmp; tmp->num = i++;
tmp->accfd = accfd;
printf("%s连接上来\n",inet_ntoa(from.sin_addr));
//创建线程
pthread_create(&(tmp->pthid),,message,(void*)(i-));
} }

【C】——网络编程-聊天室的更多相关文章

  1. Linux&C网络编程————“聊天室”

    从上周到现在一直在完成最后的项目,自己的聊天室,所以博客就没怎么跟了,今天晚上自己的聊天室基本实现,让学长检查了,也有好些bug,自己还算满意,主要实现的功能有: 登录注册 附近的人(服务器端全部在线 ...

  2. Linux&C网络编程————“聊天室”

    从上周到现在一直在完成最后的项目,自己的聊天室,所以博客就没怎么跟了,今天晚上自己的聊天室基本实现,让学长检查了,也有好些bug,自己还算满意,主要实现的功能有: 登录注册 附近的人(服务器端全部在线 ...

  3. Java进阶:基于TCP通信的网络实时聊天室

    目录 开门见山 一.数据结构Map 二.保证线程安全 三.群聊核心方法 四.聊天室具体设计 0.用户登录服务器 1.查看当前上线用户 2.群聊 3.私信 4.退出当前聊天状态 5.离线 6.查看帮助 ...

  4. Socket网络编程--聊天程序(9)

    这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也 ...

  5. Python Socket 编程——聊天室示例程序

    上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和客户端的代码了解基本的 Python Socket 编程模型.本文再通过一个例子来加强一下对 Socket 编程的 ...

  6. Socket网络编程--聊天程序(1)

    很早的一段时间,看了APUE和UNPv1了解了网络编程,但是但是只是看而已,没有具体的实践,趁现在没有什么事做,就来实践了解一下网络编程.写博客保存下来,方便以后用到的时候可以查到. 此次的聊天程序是 ...

  7. Socket网络编程--聊天程序(6)

    这一小节将增加一个用户的结构体,用于保存用户的用户名和密码,然后发给服务器,然后在服务器进行判断验证.这里就有一个问题,以前讲的就是发送字符串是使用char类型进行传输,然后在服务器进行用同样是字符串 ...

  8. Socket网络编程--聊天程序(7)

    接上一小节,本来是计划这一节用来讲数据库的增删改查,但是在实现的过程中,出现了一点小问题,也不是技术的问题,就是在字符界面上比较不好操作.比如要注册一个帐号,就需要弄个字符界面提示,然后输入数字表示选 ...

  9. Socket网络编程--聊天程序(8)

    上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能.就是通过服务器对客户端的数据进行转发到特定的用户上, 实现私聊功能的聊天程序 实现的 ...

随机推荐

  1. MySQL (1366, "Incorrect string value: '\\xF0\\x9F\\x8E\\xAC\\xE5\\x89...' for column 'description' at row 1")

    (1366, "Incorrect string value: '\\xF0\\x9F\\x8E\\xAC\\xE5\\x89...' for column 'description' at ...

  2. oracle PLSQL 多结果集嵌套循环处理优化

    oracle多结果集嵌套循环处理优化 --性能差 begin for a in (select id,name,sex,idcard from people) loop for b in (selec ...

  3. (UML总结三)UML与软件project

    学习完UML.我们要把它和之前的软件project结合起来.软件project是从总体的角度说了软件开发的步骤.保证了所开发软件的质量.而UML作为一种统一建模语言.是用来设计软件蓝图的可视化建模语言 ...

  4. hdu 1217 Arbitrage (最小生成树)

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1217 /************************************************* ...

  5. dom4j: 用dom4j生成xml后第二行空行的问题

    只需要指定: format.setNewLineAfterDeclaration(false); 即可. 例: OutputFormat format = OutputFormat.createPre ...

  6. RenderTexture动态创建纹理

    CCRenderTexture,它允许你来动态创建纹理,并且可以在游戏中重用这些纹理. 使用 CCRenderTexture非常简单 – 你只需要做以下5步就行了: 创建一个新的CCRenderTex ...

  7. 斯坦福CS231n深度学习计算机视觉

    http://study.163.com/course/introduction/1003223001.htm

  8. drools规则引擎初探

    https://www.cnblogs.com/yuebintse/p/5767996.html 1.drools是什么 Drools是为Java量身定制的基于Charles  Forgy的RETE算 ...

  9. angular学习笔记(三十)-指令(5)-link

    这篇主要介绍angular指令中的link属性: link:function(scope,iEle,iAttrs,ctrl,linker){ .... } link属性值为一个函数,这个函数有五个参数 ...

  10. SSH登录详解

    1.什么是SSH登录 SSH是一种网络协议,用于计算机之间的加密登录. 相比传统的账户密码登录,SSH提供了一种更便捷安全的登录方式. 2.SSH登录流程 登录操作如下 ssh user@host S ...