C语言实现聊天室软件
/*
common.h
*/ /*服务器端口信息*/
#define PORTLINK ".charport" /*缓存限制*/
#define MAXNAMELEN 256
#define MAXPKTLENE 2048 /*信息类型钉钉*/
#define LIST_GROUPS 0
#define JOIN_GROUP 1
#define LEAVE_GROUP 2
#define USER_TXET 3
#define JOIN_REJECTED 4
#define JOIN_ACCEPTED 5 /*数据包结构*/
typedef struct _packet
{
/*
数据包类型
数据报类型长度
数据报内容
*/
char type;
long lent;
char *text;
}Packet; extern int startserver();
extern int locateserver(); extern Packet *recvpkt(int sd);
extern int sendpkt(int sd, char typ, long len, char *buf);
extern void freepkt(Packet *msg);
/*
ChatLinker.c
*/
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<time.h>
#include<errno.h>
#include"common.h" /*
为服务器接收客户端请求做准备,
正确返回 socket 文件描述符
错误返回 -1
*/
int startserver()
{
int sd; /* socket 描述符 */
int myport; /* 服务器端口 */
const char *myname; /* 本地主机的全称 */
char linktrgt[MAXNAMELEN];
char linkname[MAXNAMELEN]; /* 调用 socket 函数创建 TCP socket 描述符 */
sd = socket(PF_INET, SOCK_STREAM, ); /* 调用bind函数将一个本地地址指派给 socket */
struct sockaddr_in server_address;
server_address.sin_family = AF_INET; /*
通配地址 INADDR_ANY 表示IP地址为 0.0.0.0,
内核在套接字被连接后选择一个本地地址
htonl函数 用于将 INADDR_ANY 转换为网络字节序
*/
server_address.sin_addr.s_addr = htonl(INADDR_ANY); /* 指派为通配端口 0,调用 bind 函数后内核将任意选择一个临时端口 */
server_address.sin_port = htons(); bind(sd, (struct sockaddr *)&server_address, sizeof(server_address)); /*
调用listen 将服务器端 socket 描述符 sd
设置为被动地监听状态
并设置接受队列的长度为20
*/
listen(sd,); /*
调用 getsockname、gethostname 和 gethostbyname
确定本地主机名和服务器端口号
*/
char hostname[MAXNAMELEN]; if (gethostname(hostname, sizeof(hostname)) != )
{
perror("gethostname");
} struct hostent *h;
h = gethostbyname(hostname); int len = sizeof(struct sockaddr); getsockname(sd, (struct sockaddr *)&server_address, &len); myname = h->h_name;
myport = ntohs(server_address.sin_port); /* 在家目录下创建符号链接'.chatport'指向linktrgt */
sprintf(linktrgt, "%s:%d", myname, myport);
/* 在头文件 common.h 中:#define PORTLINK ".chatport" */
sprintf(linkname ,"%s/%s", getenv("HOME"), PORTLINK); if (symlink(linktrgt,linkname) != )
{
fprintf(stderr, "error : server already exists\n");
return -;
} /* 准备接受客户端请求 */
printf("admin: started server on '%s' at '%d'\n", myname, myport);
return sd;
} /*
和服务器建立连接,正确返回 socket 描述符,
失败返回 -1
*/
int hooktoserver()
{
int sd; char linkname[MAXNAMELEN];
char linktrgt[MAXNAMELEN];
char *servhost;
char *servport;
int bytecnt; /* 获取服务器地址 */
sprintf(linkname, "%s/%s", getenv("HOME"), PORTLINK);
bytecnt = readlink(linkname, linktrgt, MAXNAMELEN); if (bytecnt == -)
{
fprintf(stderr,"error : no active char server\n");
return -;
}
linktrgt[bytecnt] = '\0'; /* 获得服务器 IP 地址和端口号 */
servport = index(linktrgt, ':');
*servport = '\0';
servport++;
servhost = linktrgt; /* 获得服务器 IP 地址的 unsigned short 形式 */
unsigned short number = (unsigned short) strtoul(servport, NULL, ); /*
调用函数 socket 创建 TCP 套接字
*/
sd = socket(AF_INET, SOCK_STREAM, ); /*
调用 gethostbyname() 和 connect()连接 'servhost' 的 'servport' 端口
*/
struct hostent *hostinfo;
struct sockaddr_in address; hostinfo = gethostbyname(servhost);
address.sin_addr = *(struct in_addr *)*hostinfo->h_addr_list;
address.sin_family = AF_INET;
address.sin_port = htons(number); if (connect(sd, (struct sockaddr *) &address, sizeof(address)) < )
{
perror("connecting");
exit();
} printf("admin : connected to server on '%s' at '%s'\n",
servhost,servport);
return sd;
} /* 从内核读取一个套接字的信息 */
int readn(int sd, char *buf, int n)
{
int toberead;
char *ptr; toberead = n;
ptr = buf;
while (toberead > )
{
int byteread; byteread = read(sd, ptr, toberead);
if (byteread <= )
{
if(- == byteread)
{
perror("read");
}
return ;
} toberead -= byteread;
ptr += byteread;
} return ;
} /* 接收数据包 */
Packet *recvpkt(int sd)
{
Packet *pkt; pkt = (Packet *)calloc(, sizeof(Packet));
if (!pkt)
{
fprintf(stderr, "error : unable to calloc \n");
return NULL;
} /* 读取消息类型 */
if (!readn(sd, (char *)&pkt->type, sizeof(pkt->type)))
{
free(pkt);
return NULL;
} /* 读取消息长度 */
if (!readn(sd, (char *)&pkt->lent, sizeof(pkt->lent)))
{
free(pkt);
return NULL;
} pkt->lent = ntohl(pkt->lent); /* 读取消息文本 */
if (pkt->lent > )
{
pkt->text = (char *)malloc(pkt->lent);
if (!pkt)
{
fprintf(stderr,"error : unable to malloc\n");
return NULL;
} if (!readn(sd, pkt->text, pkt->lent))
{
freepkt(pkt);
return NULL;
}
} return pkt;
} /* 发送数据包 */
int sendpkt(int sd, char typ, long len, char *buf)
{
char tmp[];
long siz; /* 把包的类型和长度写入套接字 */
bcopy(&typ, tmp, sizeof(typ));
siz = htonl(len);
bcopy((char *) &siz, tmp + sizeof(typ), sizeof(len));
write(sd, tmp, sizeof(typ)+sizeof(len)); /* 把消息文本写入套接字 */
if (len > )
{
write(sd, buf, len);
} return ;
} /* 释放数据包占用的内存空间 */
void freepkt(Packet *pkt)
{
free(pkt->text);
free(pkt);
}
/*
ChatServer.c
*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<time.h>
#include<errno.h>
#include"common.h" /* 聊天室成员信息 */
typedef struct _member
{ char *name; /*成员姓名*/
int sock; /*成员socket描述符*/
int grid; /*成员所属聊天室*/
struct _member *next; /*下一个成员*/
struct _member *prev; /*前一个成员*/
}Member; /* 聊天室信息 */
typedef struct _group
{
char *name; /* 聊天室名字 */
int capa; /* 聊天室最大容量(人数) */
int occu; /* 当前占有率(人数) */
struct _member *mems; /* 记录聊天室内所有成员信息的链表 */
}Group; /* 所有聊天室的信息表 */
Group *group;
int ngroups; /* 通过聊天室名字找到聊天室 ID */
int findgroup(char *name)
{
int grid; for (grid = ; grid < ngroups; grid++)
{
if (strcmp(group[grid].name, name) == )
{
return grid;
}
} return -;
} /* 通过室成员名字找到室成员的信息 */
Member *findmemberbyname(char *name)
{
int grid; for (grid = ; grid < ngroups; grid++)
{
Member *memb; for (memb = group[grid].mems; memb; memb = memb->next)
{
if ( == strcmp(memb->name, name))
{
return memb;
}
}
} return NULL;
} /* 通过 socket 描述符找到室成员的信息 */
Member *findmemberbysock(int sock)
{
int grid; for (grid = ; grid < ngroups; grid++)
{
Member *memb; for (memb = group[grid].mems; memb; memb = memb->next)
{
if (memb->sock == sock)
{
return memb;
}
}
} return NULL;
} /* 退出前的清理工作 */
void cleanup()
{
char linkname[MAXNAMELEN]; sprintf(linkname,"%s/%s",getenv("HOME"),PORTLINK);
unlink(linkname);
exit();
} int initgroups(char *groupsfile)
{
FILE *fp;
char name[MAXNAMELEN];
int capa;
int grid; /* 打开存储聊天室信息的配置文件 */
fp = fopen(groupsfile,"r");
if (!fp)
{
fprintf(stderr,"error : unable to open file '%s'\n",groupsfile);
return ;
} /* 从配置文件中读取聊天室的数量 */
fscanf(fp,"%d",&ngroups); group = (Group *)calloc(ngroups,sizeof(Group));
if (!group)
{
fprintf(stderr,"error : unable to calloc\n");
return ;
} /* 从配置文件读取聊天室信息 */
for (grid = ; grid < ngroups; grid++)
{
/* 读取聊天室名和容量 */
if (fscanf(fp,"%s %d", name, &capa) != )
{
fprintf(stderr, "error : no info on froup %d\n",grid + );
return ;
}
/* 将信息存进 group 结构 */
group[grid].name = strdup(name);
group[grid].capa = capa;
group[grid].occu = ;
group[grid].mems = NULL;
}
return ;
} int main(int argc, char *argv[])
{
int servsock; /* 聊天室服务器端监听 socket 描述符 */
int maxsd; /* 连接的客户端 socket 描述符的最大值 */
fd_set livesdset, tempset; /* 客户端 sockets 描述符集 */ if (argc != )
{
fprintf(stderr,"usage : %s <group-file>\n",argv[]);
exit();
} /* 调用 initgroups 函数,初始化聊天室信息 */
if (!initgroups(argv[]))
{
exit();
} /* 设置信号处理函数 */
signal(SIGTERM, cleanup);
signal(SIGINT,cleanup); /* 准备接受请求 */ /*
定义在 "chatlinker.c" 文件中
主要完成创建服务器套接字,绑定端口号,
并设置把套接字为监听状态
*/
servsock = startserver();
if (- == servsock)
{
return -;
} maxsd = servsock; /* 初始化描述符集 */
FD_ZERO(&livesdset);
FD_ZERO(&tempset);
/*
打开服务器监听套接字的套接字
描述符 servsock 对应的fd_set 比特位
*/
FD_SET(servsock, &livesdset); while()
{
int sock; /* 特别注意 tempset 作为 select 参数时是一个 "值-结果" 参数,
select 函数返回时,tempset 中打开的比特位只是读就绪的 socket
描述符,所以我们每次循环都要将其更新为我们需要内核测试读就绪条件
的 socket 描述符集合 livesdset */
tempset = livesdset; /* 调用 select 函数等待已连接套接字上的包和来自
新的套接字的链接请求 */
select(maxsd+, &tempset, NULL, NULL, NULL); /* 循环查找来自客户机的请求 */
for (sock = ; sock <= maxsd; sock++)
{
/* 如果是服务器监听 socket,则跳出接收数据包环节,执行接受连接 */
if (sock == servsock)
{
continue;
} /* 有来自客户 socket 的消息 */
if (FD_ISSET(sock, &tempset))
{
Packet *pkt;
pkt = recvpkt(sock);
if (!pkt)
{
/* 客户机断开了连接 */
char *clientname; /* 使用 gethostbyaddr,getpeername 函数得到 client 的主机名 */
socklen_t len;
struct sockaddr_in addr;
len = sizeof(addr);
if (getpeername(sock, (struct sockaddr *)&addr, &len) == )
{
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
struct hostent *he;
he = gethostbyaddr(&s->sin_addr, sizeof(struct in_addr),
AF_INET);
clientname = he->h_name;
}
else
{
printf("Cannot get peer name");
} printf("admin : disconnect from '%s' at '%d'\n",
clientname, sock); leavegroup(sock);
close(sock);
FD_CLR(sock, &livesdset);
}
else
{
char *gname, *mname; /* 基于消息类型采取行动 */
switch (pkt->type)
{
case LIST_GROUPS:
listgroups(sock);
break;
case JOIN_GROUP:
gname = pkt->text;
mname = gname + strlen(gname) + ;
joingroup(sock, gname, mname);
break;
case LEAVE_GROUP:
leavegroup(sock);
break;
case USER_TXET:
relaymsg(sock, pkt->text);
break;
} freepkt(pkt);
}
}
} struct sockaddr_in remoteaddr; /* 客户机地址结构 */
socklen_t addrlen; /* 有来自新的客户机的连接请求请求 */
if (FD_ISSET(servsock, &tempset))
{
/* 已连接的 socket 描述符 */
int csd; /* 接受一个新的连接请求 */
addrlen = sizeof(remoteaddr);
csd = accept(servsock, (struct sockaddr *)&remoteaddr, &addrlen); if (- != csd)
{
char *clientname;
struct hostent *h;
/* 使用 gethostbyaddr 函数得到 client 的主机名 */
h = gethostbyaddr((char *)&remoteaddr.sin_addr.s_addr,
sizeof(struct in_addr), AF_INET); if (h != (struct hostent *))
{
clientname = h->h_name;
}
else
{
printf("gethostbyaddr failed\n");
} /* 显示客户机的主机名和对应的 socket 描述符 */
printf("admin : connect from '%s' at '%d'\n",
clientname, csd); /* 将该连接的套接字描述符 csd 加入livesdset */
FD_SET(csd, &livesdset); /* 保持 maxsd 记录的是最大的套接字描述符 */
if (csd > maxsd)
{
maxsd = csd;
}
}
else
{
perror("accept");
exit();
}
}
}
} /* 把所有聊天室的信息发给客户端 */
int listgroups(int sock)
{
int grid;
char pktbufr[MAXPKTLENE];
char *bufrptr;
long bufrlen; /* 每一块信息在字符串中用 NULL 分割 */
bufrptr = pktbufr;
for (grid = ; grid < ngroups; grid++)
{
/* 获取聊天室名字 */
sprintf(bufrptr,"%s", group[grid].name);
bufrptr += strlen(bufrptr) + ; /* 获取聊天室容量 */
sprintf(bufrptr, "%d", group[grid].capa);
bufrptr += strlen(bufrptr) + ; /* 获取聊天室占有率 */
sprintf(bufrptr, "%d",group[grid].occu);
bufrptr += strlen(bufrptr) + ;
} bufrlen = bufrptr - pktbufr; /* 发送消息给回复客户机的请求 */
sendpkt(sock, LIST_GROUPS, bufrlen, pktbufr);
return ;
} /* 加入聊天室 */
int joingroup(int sock, char *gname, char *mname)
{
int grid;
Member *memb; /* 根据聊天室名获得聊天室 ID */
grid = findgroup(gname);
if (grid == -)
{
char *errmsg = "no such group";
sendpkt(sock, JOIN_REJECTED, strlen(errmsg), errmsg);
return ;
} /* 检查是否聊天室成员名字已被占用 */
memb = findmemberbyname(mname); /* 如果聊天室成员名已存在,则返回错误消息 */
if (memb)
{
char *errmsg = "member name already exists";
sendpkt(sock, JOIN_REJECTED, strlen(errmsg), errmsg);
return ;
} /* 检查聊天室是否已满 */
if (group[grid].capa == group[grid].occu)
{
char *errmsg = "room is full";
sendpkt(sock, JOIN_REJECTED, strlen(errmsg), errmsg);
return ;
} memb = (Member *)calloc(, sizeof(Member));
if (!memb)
{
fprintf(stderr, "error : unable to calloc\n");
cleanup();
} memb->name = strdup(mname);
memb->sock = sock;
memb->grid = grid;
memb->prev = NULL;
memb->next = group[grid].mems;
if (group[grid].mems)
{
group[grid].mems->prev = memb;
} group[grid].mems = memb;
printf("admin: '%s' join '%s'\n",mname, gname); /* 更新聊天室的在线人数 */
group[grid].occu++; sendpkt(sock, JOIN_ACCEPTED, , NULL); return ;
} /* 离开聊天室 */
int leavegroup(int sock)
{
Member *memb; /* 得到聊天室成员信息 */
memb = findmemberbysock(sock);
if (!memb)
{
return ;
} /* 从聊天室信息结构中删除 memb 成员 */
if (memb->next)
{
memb->next->prev = memb->prev; /* 在聊天室成员链表的尾部 */
} if (group[memb->grid].mems == memb)
{
group[memb->grid].mems = memb->next; /* 在聊天室成员链表的头部 */
}
else
{
memb->prev->next = memb->next; /* 在聊天室成员链表的中部 */
} printf("admin: '%s' left '%s'\n",
memb->name, group[memb->grid].name); /* 更新聊天室的占有率 */
group[memb->grid].occu--; free(memb->name);
free(memb);
return ;
} /* 把成员的消息发送给其他聊天室成员 */
int relaymsg(int sock, char *text)
{
Member *memb;
Member *sender;
char pktbufr[MAXPKTLENE];
char *bufrptr;
long bufrlen; /* 根据 socket 描述符获得该聊天室成员的信息 */
sender = findmemberbysock(sock);
if (!sender)
{
fprintf(stderr,"strange : no member at %d\n",sock);
return ;
} /* 把发送者的姓名添加到消息文本前边 */
bufrptr = pktbufr;
strcpy(bufrptr, sender->name);
bufrptr += strlen(bufrptr) + ;
strcpy(bufrptr, text);
bufrptr += strlen(bufrptr) + ;
bufrlen = bufrptr - pktbufr; for (memb = group[sender->grid].mems; memb; memb = memb->next)
{
if (memb->sock == sock)
{
continue;
}
/* 给聊天室其他成员发送消息(TCP是全双工的) */
sendpkt(memb->sock, USER_TXET, bufrlen, pktbufr);
} printf("%s : %s", sender->name, text);
return ; }
/*
ChatClient.c
*/
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<time.h>
#include<errno.h>
#include"common.h" #define QUIT_STRING "/end" /*打印聊天室名单*/
void showgroups(long lent, char *text)
{
char *tptr; tptr = text;
printf("%15s %15s %15s\n","group","capacity","occupancy");
while (tptr < text + lent)
{
char *name, *capa, *occu; name = tptr;
tptr = name + strlen(name) + ;
capa = tptr;
tptr = capa + strlen(capa) + ;
occu = tptr;
tptr = occu + strlen(occu) + ; printf("%15s %15s %15s\n",name, capa, occu);
}
} /*加入聊天室*/
int joingroup(int sock)
{
Packet *pkt;
char bufr[MAXPKTLENE];
char *bufrptr;
int bufrlen;
char *gname;
char *mname; /*请求聊天信息*/
sendpkt(sock, LIST_GROUPS, , NULL); /*接收聊天室信息回复*/
pkt = recvpkt(sock);
if (!pkt)
{
fprintf(stderr,"error: server died\n");
exit();
} if (pkt->type != LIST_GROUPS)
{
fprintf(stderr,"error: unexpected reply from server\n");
exit();
} /*显示聊天室*/
showgroups(pkt->lent, pkt->text); /*从标准输入读入聊天室名*/
printf("which group? ");
fgets(bufr, MAXPKTLENE, stdin);
bufr[strlen(bufr) - ] = '\0'; /*此时可能用户想退出*/
if (strcmp(bufr, "") == ||
strncmp(bufr, QUIT_STRING, strlen(QUIT_STRING)) == )
{
close(sock);
exit();
}
gname = strdup(bufr); /*读入成员名字*/
printf("what nickname? ");
fgets(bufr, MAXPKTLENE, stdin);
bufr[strlen(bufr) - ] = '\0'; /*此时可能用户想退出*/
if (strcmp(bufr, "") == ||
strncmp(bufr, QUIT_STRING, strlen(QUIT_STRING)) == )
{
close(sock);
exit();
}
mname = strdup(bufr); /*发送加入聊天室信息*/
bufrptr = bufr;
strcpy(bufrptr, gname);
bufrptr += strlen(bufrptr) + ;
strcpy(bufrptr, mname);
bufrptr += strlen(bufrptr) + ;
bufrlen = bufrptr - bufr;
sendpkt(sock, JOIN_GROUP, bufrlen, bufr); /* 读取来自服务器的回复 */
pkt = recvpkt(sock);
if (!pkt)
{
fprintf(stderr, "error : server died\n");
exit();
} if (pkt->type != JOIN_ACCEPTED && pkt->type != JOIN_REJECTED)
{
fprintf(stderr, "error : unexpected reply from server\n");
exit();
} /* 如果拒绝显示其原因 */
if (pkt->type == JOIN_REJECTED)
{
printf("admin : %s\n", pkt->text);
free(gname);
free(mname);
return ;
}
else
{
printf("admin: join '%s' as '%s'\n",gname, mname);
free(gname);
free(mname);
return ;
}
} int main(int argc, char **argv)
{
int sock; /* 用户输入合法性检测 */
if (argc != )
{
fprintf(stderr,"usage : %s\n", argv[]);
exit();
} /* 与服务器连接 */
sock = hooktoserver();
if (sock == -)
{
exit();
} /* 清除标准输出缓冲区 */
fflush(stdout); /* 初始化描述符集 */
fd_set clientfds, tempfds;
FD_ZERO(&clientfds);
FD_ZERO(&tempfds);
FD_SET(sock, &clientfds); /* 设置服务器套接字在clientfds中的比特位 */
FD_SET(, &clientfds); /* 设置标准输入在 clientfds 中的比特位 */ while()
{
/* 加入聊天室 */
if (!joingroup(sock))
{
continue;
} while()
{
/* 调用 select 函数同时监测键盘和服务器信息 */
tempfds = clientfds; if (select(FD_SETSIZE, &tempfds, NULL, NULL, NULL) == -)
{
perror("select");
exit();
} /* 对于所有在 tempfds 中被置位的文件描述符,检测它是否是套接字描述符,
如果是,意味服务器传来消息。如果它文件描述符是 0,则意味有来自用户
键盘的输入要发送给服务器 */ /* 处理服务器传来信息 */
if(FD_ISSET(sock, &tempfds))
{
Packet *pkt;
pkt = recvpkt(sock);
if (!pkt)
{
fprintf(stderr, "error: server died\n");
exit();
} /* 显示消息文本 */
if (pkt->type != USER_TXET)
{
fprintf(stderr,"error: unexpected reply from server\n");
exit();
} printf("%s: %s", pkt->text, pkt->text + strlen(pkt->text) + );
freepkt(pkt);
} if (FD_ISSET(, &tempfds))
{
char bufr[MAXPKTLENE];
fgets(bufr, MAXPKTLENE, stdin); /* 处理键盘输入 */
if (strncmp(bufr, QUIT_STRING, strlen(QUIT_STRING)) == )
{
sendpkt(sock, LEAVE_GROUP, , NULL);
break;
} sendpkt(sock, USER_TXET, strlen(bufr) + , bufr);
}
}
}
}
/*
groups
*/ ind
uk
us
aus
/*
makefile
*/
.c.o:
gcc -g -c $? # compile client and server
all: chatclient chatserver # compile client only
chatclient: chatclient.o chatlinker.o
gcc -g -o chatclient chatclient.o chatlinker.o # compile server program
chatserver: chatserver.o chatlinker.o
gcc -g -o chatserver chatserver.o chatlinker.o .PHONY:clean clean:
rm chatlinker.o chatclient.o chatserver.o chatclient chatserver
运行结果:
C语言实现聊天室软件的更多相关文章
- C语言实现聊天室(windows版本)
来源:微信公众号「编程学习基地」 目录 C语言聊天室 运行效果 分析设计 多线程 线程的同步 服务端设计 遇到的问题 C语言聊天室 基于 tcp 实现群聊功能,本项目设计是在windows环境下基于套 ...
- 奇妙的go语言(聊天室的开发)
[ 声明:版权全部,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 这是一篇关于聊天室开发的博客,原来文章的地址来自于此.这篇文章非常具有代表性,对于代码中的函数 ...
- Golang语言快速上手到综合实战高并发聊天室
需要的联系我:QQ:1844912514 Go是Google开发的一种编译型,可并行化,并具有垃圾回收功能的编程语言.2015,Go迎来了全迸发的一年.时隔一年,回头再看,Go已跻身主流编程语言行列. ...
- 视频聊天室可以用php制作吗?
首先,告诉你单纯用php制作视频聊天室是实现不了的,需要配合其他技术手段一起操作,例如和FLASH配合,使用FLASH获取语音(FLASH可以获取访问端的设备,例如摄像头). PHP运行在服务器端,是 ...
- 对聊天室项目的NABCD的分析
NABCD需求分析: 需求(N):我们的项目是制作一个局域网内的聊天室软件,为了解决一个公司或者小团体内小范围的局域的简单通讯问题,我们针对的需求是简单与安全. 做法(A):用Java来实现一个C/S ...
- Go语言实践_实现一(服务器端)对多(客户端)在线聊天室
一.目的 运用Go语言中的goroutine和通道实现一个简单的一个服务器端对多个客户端的在线聊天 软件环境:Goland,Go1.9 代码仓库链接 二.设计思路 与一对一的设计思路类似,就是加了个线 ...
- Go语言实践_实现一(客户端)对一(服务器端)聊天室
一.目的 使用Go语言实现一个服务器端与客户端的聊天室. 软件:Goland,Go1.9 代码仓库地址 二.思路 1,首先启动服务器端,使用listen_socket函数监听IP地址上的客户端连接: ...
- 使用Go语言+Protobuf协议完成一个多人聊天室
软件环境:Goland Github地址 一.目的 之前用纯逻辑垒完了一个可登入登出的在线多人聊天室(代码仓库地址),这次学习了Protobuf协议,于是想试着更新下聊天室的版本. 主要目的是为了掌握 ...
- 基于tcp和多线程的多人聊天室-C语言
之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室. 具体的实现过程: 服务器端:绑定socket对象->设置监听数-> ...
随机推荐
- vue组件、自定义指令、路由
1.vue组件 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的 ...
- POSIX 正则表达式 BRE与ERE的差异
BRE,标准正则表达式,basic regular expressions ERE,扩展正则表达式,Extended Regular Expressions POSIX 正则表达式 传统上,POSIX ...
- 【题解】Diferenc-Diferencija [SP10622]
[题解]Diferenc-Diferencija [SP10622] 传送门:\(\text{Diferenc-Diferencija}\) \(\text{[SP10622]}\) [题目描述] 序 ...
- DOM的回流和重绘(重排、重绘)
什么是DOM回流? 页面渲染时,我们对HTML结构简单的增删查改时,浏览器会对所有的dom进行重新排序,这就i是DOM回流,严重影响浏览器性能 DOM的回流和重绘: **DOM的回流**:当页面中元素 ...
- 2-Rocketmq产品架构(参考阿里云)
参考链接:https://help.aliyun.com/document_detail/112008.htm
- 递归删除文件和文件夹(bat)
递归删除当前目录下指定的文件和文件夹,使用了通配符,Win10下亲测有效,仅供参考! Batch Code 123456 @echo off echo del file... for /r % ...
- 【转载】Gradle学习 第三章:教程
转载地址:http://ask.android-studio.org/?/article/15 3.1. Getting Started 入门The following tutorials intro ...
- android 第三方开源库 学习汇总
依赖注入框架ButterKnife https://github.com/JakeWharton/butterknife 学习过程 专注于android的View注入框架,并不支持其他方面 ...
- <Android Studio> 3.打包APK
我的IDE版本是 3.5 我希望输出的apk文件格式是: 名称_v版本_release/debug_日期 时间.apk 步骤: 1.打开build.gradle 末尾添加如下代码 def releas ...
- IDEA安装(2019.2版)
IDEA安装(2019.2版) 前段时间在公司实习接触过现下很火的 IDE,这里我根据搜集到的资料以及自己的实际操作整合了这篇博客,包括了安装和破解 IDEA,借此打开学习之旅. IntelliJ ...