网络编程
1.跟前面进程间通信的关系
前面学习的进程间通信方式(管道,信号,共享内存,消息队列,信号量)都只能用于同一台主机内部的不同进程间
网络编程(套接字编程)也是属于进程间通信的一种,网络编程既可以在同一台主机(本地通信)内部不同进程间通信,也能在不同主机间的进程通信
2.知识点概念
网络有关的概念
传输层的两个协议tcp和udp
tcp单向通信
tcp双向通信
tcp点播
tcp广播
udp单向通信
udp双向通信
多路复用
网络有关的概念
1.网络模型
计算机科学家为了方便我们这些小白理解复杂的网络通信过程,特意把网络分成了不同的层次(方便我们去理解)
网络通信协议
人为制定的游戏规则,网络模型中每个层次都有自己的通信协议
通信协议族
ip地址分为两种IPV4地址和IPV6
IPV4地址:最常用的ip地址,32位的地址 比如: 192.168.16.2(点分十进制ip)
IPV6地址:全世界上网的人多了,ip不够用,扩展位数为128位
端口号
本质上是个无符号的短整型数字(0---65535之间),用来区分同一台主机内部不同的网络进程
程序员可以自己指定端口号,1024以内的端口号最好不要使用(很多被操作系统占用)常用的网络模型
第一种:OSI七层模型
应用层 作用:开发特定的应用程序需要用到其中的部分协议 http协议(超文本传输协议) ftp协议(文件传输协议) telnet(远程登录)
会话层 作用:建立会话,对数据加密,解密
表示层 作用:数据的封装,解析
传输层 作用:解决数据在网络中传输的问题 tcp协议和udp协议
网络层(ip层) 作用:解决路由(数据采取何种路径发送出去最优)问题 ip协议
数据链路层 作用:开发网卡的驱动,需要深入了解这个层次的协议
物理层 网络接口,真实的网卡
第二种:TCP/IP模型
把七层模型合并简化成四层
应用层(原来的应用层,会话层,表示层合并)
传输层
网际层
网络接口层(数据链路层和物理层合并)
tcp协议
1.tcp协议的通信流程(原理)
2.tcp协议相关的接口函数
(1)创建套接字(买手机)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:成功 返回套接字的文件描述符
失败 -1
参数:domain --》通信协议族
AF_INET --》表示ipv4地址协议族
AF_INET6 --》表示ipv6地址协议族
type --》套接字的类型
SOCK_STREAM --》tcp套接字(流式套接字,数据流套接字)
SOCK_DGRAM --》udp套接字(数据报套接字)
protocol --》扩展协议,默认设置0,不用理会
(2)绑定ip和端口号
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); //通用性强,使用ipv4或者ipv6都可以
假设:int bind(int sockfd, const struct sockaddr_in *addr,socklen_t addrlen); //局限性只能绑定ipv4地址
假设:int bind(int sockfd, const struct sockaddr_in6 *addr,socklen_t addrlen); //局限性只能绑定ipv6地址
参数: sockfd --》套接字文件描述符
addr --》
struct sockaddr 通用地址结构体,兼容ipv4和ipv6
struct sockaddr_in ipv4地址结构体(保存ipv4地址和端口号的)
{
sin_family; //保存地址协议族AF_INET
struct in_addr sin_addr; //保存你要绑定的ip地址(一定是绑定本地主机的ip)
sin_port; //保存你要绑定的端口号
}
sin_addr成员又是一个结构体
{
in_addr_t s_addr; //最终用来存放ipv4地址的那个变量
}
struct sockaddr_in6 ipv6地址结构体(保存ipv6地址和端口号的)
addrlen --》地址结构体的大小 sizeof()
(3)ip地址和端口号的转换--》大小端转换(字节序转换)
大端序:数据的高字节存放在低地址,低字节存放在高地址
小端序:数据的高字节存放在高地址,低字节存放在低地址
ubuntu系统:采用的是小端序存放数据
主机字节序:指的就是小端序
计算机网络协议:采用的是大端序
网络字节序:指的就是大端序
主机字节序---》网络字节序
#include <netinet/in.h>
#include <arpa/inet.h>
转换ip地址
in_addr_t inet_addr(const char *cp);
返回值:成功 返回转换得到的网络字节序ip
参数:cp --》你要转换的那个字符串ip地址
转换端口号
uint16_t htons(uint16_t hostshort);
返回值:成功 返回转换得到的网络字节序端口号
参数:hostshort --》你要转换的端口号
网络字节序---》主机字节序
转换ip地址
char *inet_ntoa(struct in_addr in);
返回值:成功 返回字符串ip地址
参数:ipv4地址结构体中用来存放大端序ip的那个变量
转换端口号
uint16_t ntohs(uint16_t netshort);
(4)连接服务器
看到地址结构体指针,条件反射知道究竟存放谁的ip,谁的端口号
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数: sockfd --》套接字文件描述符
addr --》存放服务器的ip和端口号
addrlen --》地址结构体的大小 sizeof()
(5)收发信息
有两组函数可以使用
第一组:read/write
第二组:recv/send
ssize_t send(int sockfd, const void *buf, size_t len, int flags); //发送信息
返回值:成功 发送的字节数
失败 -1
参数:sockfd --》套接字文件描述符
buf --》存放你要发送的内容
len --》你打算发送多少字节的数据
flags --》设置为0
ssize_t recv(int sockfd, void *buf, size_t len, int flags); //接收信息
返回值:成功 接收到的字节数
失败 -1
断开 0
参数:sockfd --》套接字文件描述符
buf --》存放你接收的内容
len --》你打算接收多少字节的数据
flags --》设置为0
注意:如果对方一直没有信息发送过来recv/read会一直阻塞
(6)监听 --》待机
int listen(int sockfd, int backlog);
参数:sockfd --》套接字文件描述符
backlog --》最多允许多少个客户端同时连接一个服务器,一般5--10随便写个数字
比如:listen(sock,5); 最多运行5个客户端同时连接,但是如果客户端是依次错开连接服务器的,可以超过5个
(7)接受客户端的连接请求(重点)
int accept(int socket, struct sockaddr *address,socklen_t *address_len);
返回值:成功 返回值新的套接字文件描述符(用来区分不同的客户端,服务器要跟哪个客户端聊天,使用对应的套接字即可)
失败 -1
参数:sockfd --》套接字文件描述符
address --》不需要你初始化,自动存放目前连接你的那个客户端的ip和端口号
address_len --》地址结构体大小,要求是指针
总结accept函数的特点
特点一:如果没有客户端连接服务器,服务器会一直阻塞在accept的位置
如果有客户端成功连接服务器,accept就不会阻塞,返回新的套接字用于通信
特点二:accept每连接成功一个客户端,都会产生不同的新套接字
遇到的问题和现象
问题一:
客户端强行退出会导致服务器recv不阻塞了???
原因:
无论客户端/服务器,只要断开了,recv/read都不会阻塞,并且返回0表示对方断开了
解决方法:
条件判断,发现对方断开,你也退出程序
问题二:
首次运行程序没有任何问题,强行退出以后,再次运行程序出现:绑定ip和端口号失败! : Address already in use错误
原因:
linux系统当你的程序退出的时候,绑定的端口号不会立马释放调用(需要等待大概半分钟才会释放),所以你急急忙忙再次运行,提示这种错误(端口号被占用了)
解决方法:
方法一:耐心等待半分钟以后再来运行即可
方法二:更换新的端口号(通过主函数传参去更换比较方便),重新编译程序运行
方法三:使用setsockopt这个函数设置取消端口绑定限制
int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);
参数:socket --》套接字文件描述符
level --》SOL_SOCKET
option_name --》SO_REUSEADDR 重复使用端口号
tcp单向通讯
#include "myhead.h"
/*
通过男女朋友聊天的例子,辅助理解tcp通信的流程和接口函数
男朋友的代码--》男朋友(客户端)发送信息给女朋友(服务器端)
老师为了上课演示现象方便--》客户端和服务器都在同一个主机上
同一台主机--》端口号不能一样
不同的主机--》端口号相同也行,不相同也行
*/
int main()
{
char sbuf[100];
int tcpsock;
int ret;
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //男朋友的ip地址
bindaddr.sin_port=htons(10000); //男朋友的端口号
//定义ipv4地址结构体变量存放女朋友(服务器)的ip和端口号
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //女朋友的ip地址
serveraddr.sin_port=htons(20000); //女朋友的端口号
//创建套接字
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建套接字失败!\n");
return -1;
}
//绑定ip和端口号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败!\n");
return -1;
}
//连接服务器
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
if(ret==-1)
{
perror("连接服务器失败!\n");
return -1;
}
while(1)
{
bzero(sbuf,100);
printf("请输入要发送给服务器(女朋友)的信息!\n");
scanf("%s",sbuf);
//发送给服务器
send(tcpsock,sbuf,strlen(sbuf),0);
}
close(tcpsock);
return 0;
}
#include "myhead.h"
/*
通过男女朋友聊天的例子,辅助理解tcp通信的流程和接口函数
女朋友的代码--》男朋友(客户端)发送信息给女朋友(服务器端)
老师为了上课演示现象方便--》客户端和服务器都在同一个主机上
同一台主机--》端口号不能一样
不同的主机--》端口号相同也行,不相同也行
*/
int main()
{
char sbuf[100];
int tcpsock;
int newsock;
int ret;
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //女朋友的ip地址
bindaddr.sin_port=htons(20000); //女朋友的端口号
//定义ipv4地址结构体变量存放连接成功的那个客户端ip和端口号
struct sockaddr_in clientaddr;
int addrsize=sizeof(clientaddr);
//创建套接字
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建套接字失败!\n");
return -1;
}
//绑定ip和端口号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败!\n");
return -1;
}
//监听
ret=listen(tcpsock,6);
if(ret==-1)
{
perror("监听失败!\n");
return -1;
}
printf("旧的套接字是:%d\n",tcpsock);
//接收客户端的连接请求
newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);
if(newsock==-1)
{
perror("接收客户端的连接请求失败!\n");
return -1;
}
printf("新的套接字是:%d\n",newsock);
while(1)
{
bzero(sbuf,100);
//接收客户端发送过来的信息
recv(newsock,sbuf,100,0);
//recv(tcpsock,sbuf,100,0); //错误示范,用错了套接字
printf("服务器收到的内容是:%s\n",sbuf);
}
close(tcpsock);
return 0;
}
tcp双向通讯
#include "myhead.h"
/*
tcp双向通信
*/
int tcpsock;
void *recvmsgfromserver(void *arg)
{
char rbuf[100];
int ret;
while(1)
{
bzero(rbuf,100);
ret=recv(tcpsock,rbuf,100,0);
if(ret==0) //说明对方(服务器断开了)
exit(0);
printf("服务器回复的信息是:%s\n",rbuf);
}
}
int main()
{
char sbuf[100];
int ret;
pthread_t id;
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //男朋友的ip地址
bindaddr.sin_port=htons(10000); //男朋友的端口号
//定义ipv4地址结构体变量存放女朋友(服务器)的ip和端口号
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //女朋友的ip地址
serveraddr.sin_port=htons(20000); //女朋友的端口号
//创建套接字
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建套接字失败!\n");
return -1;
}
//绑定ip和端口号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败!\n");
return -1;
}
//连接服务器
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
if(ret==-1)
{
perror("连接服务器失败!\n");
return -1;
}
//创建一个子线程专门用来接收信息
pthread_create(&id,NULL,recvmsgfromserver,NULL);
//主线程专门用来发送信息
while(1)
{
bzero(sbuf,100);
printf("请输入要发送给服务器(女朋友)的信息!\n");
scanf("%s",sbuf);
//发送给服务器
send(tcpsock,sbuf,strlen(sbuf),0);
}
close(tcpsock);
return 0;
}
#include "myhead.h"
/*
服务器的代码
*/
int newsock;
void *sendmsgtoclient(void *arg)
{
char rbuf[100];
while(1)
{
bzero(rbuf,100);
printf("请输入要发送给客户端的信息!\n");
scanf("%s",rbuf);
send(newsock,rbuf,strlen(rbuf),0);
}
}
int main()
{
char sbuf[100];
int tcpsock;
pthread_t id;
int ret;
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //女朋友的ip地址
bindaddr.sin_port=htons(20000); //女朋友的端口号
//定义ipv4地址结构体变量存放连接成功的那个客户端ip和端口号
struct sockaddr_in clientaddr;
int addrsize=sizeof(clientaddr);
//创建套接字
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建套接字失败!\n");
return -1;
}
//绑定ip和端口号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败!\n");
return -1;
}
//监听
ret=listen(tcpsock,6);
if(ret==-1)
{
perror("监听失败!\n");
return -1;
}
//接收客户端的连接请求
newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);
if(newsock==-1)
{
perror("接收客户端的连接请求失败!\n");
return -1;
}
//创建一个线程专门用来发送信息
pthread_create(&id,NULL,sendmsgtoclient,NULL);
//主线程专门用来接收信息
while(1)
{
bzero(sbuf,100);
//接收客户端发送过来的信息
ret=recv(newsock,sbuf,100,0);
if(ret==0) //说明客户端断开了
exit(0);
printf("服务器收到的内容是:%s\n",sbuf);
}
close(tcpsock);
return 0;
}
使用setsockopt这个函数设置取消端口绑定限制
#include "myhead.h"
/*
tcp双向通信
*/
int tcpsock;
void *recvmsgfromserver(void *arg)
{
char rbuf[100];
int ret;
while(1)
{
bzero(rbuf,100);
ret=recv(tcpsock,rbuf,100,0);
if(ret==0) //说明对方(服务器断开了)
exit(0);
printf("服务器回复的信息是:%s\n",rbuf);
}
}
int main(int argc,char **argv)
{
char sbuf[100];
int ret;
pthread_t id;
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //男朋友的ip地址
bindaddr.sin_port=htons(10000); //男朋友的端口号
//定义ipv4地址结构体变量存放女朋友(服务器)的ip和端口号
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //女朋友的ip地址
serveraddr.sin_port=htons(20000); //女朋友的端口号
//创建套接字
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建套接字失败!\n");
return -1;
}
//取消端口号绑定限制
int on=1;
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//绑定ip和端口号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败!\n");
return -1;
}
//连接服务器
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
if(ret==-1)
{
perror("连接服务器失败!\n");
return -1;
}
//创建一个子线程专门用来接收信息
pthread_create(&id,NULL,recvmsgfromserver,NULL);
//主线程专门用来发送信息
while(1)
{
bzero(sbuf,100);
printf("请输入要发送给服务器(女朋友)的信息!\n");
scanf("%s",sbuf);
//发送给服务器
send(tcpsock,sbuf,strlen(sbuf),0);
}
close(tcpsock);
return 0;
}
tcp点播、广播代码实现
1.点播 p2p(point to point)
含义:一个客户端跟另外一个客户端通过服务器转发实现通信
#include "myhead.h"
/*
tcp双向通信
*/
int tcpsock;
void *recvmsgfromserver(void *arg)
{
char rbuf[2048];
int ret;
while(1)
{
bzero(rbuf,2048);
ret=recv(tcpsock,rbuf,2048,0);
if(ret==0) //说明对方(服务器断开了)
exit(0);
printf("服务器回复的信息是:%s\n",rbuf);
}
}
int main()
{
char sbuf[2048];
char someip[20];
char someport[6];
char truemsg[100];
char broadcastmsg[100];
int ret;
pthread_t id;
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //男朋友的ip地址
bindaddr.sin_port=htons(10001); //男朋友的端口号
//定义ipv4地址结构体变量存放女朋友(服务器)的ip和端口号
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //女朋友的ip地址
serveraddr.sin_port=htons(20000); //女朋友的端口号
//创建套接字
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建套接字失败!\n");
return -1;
}
//取消端口号绑定限制
int on=1;
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//绑定ip和端口号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败!\n");
return -1;
}
//连接服务器
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
if(ret==-1)
{
perror("连接服务器失败!\n");
return -1;
}
//创建一个子线程专门用来接收信息
pthread_create(&id,NULL,recvmsgfromserver,NULL);
//主线程专门用来发送信息
while(1)
{
bzero(sbuf,2048);
printf("请输入要发送给服务器的信息!\n");
printf("三种选择: 1. getlist表示获取在线客户端信息 2. p2p点播 3. all广播!\n");
scanf("%s",sbuf);
//判断你输入的内容
if(strcmp(sbuf,"getlist")==0) //表示你这个客户端想让服务器发送在线客户端信息给你
{
//发送给服务器
send(tcpsock,sbuf,strlen(sbuf),0);
}
else if(strcmp(sbuf,"p2p")==0) //表示我想要点播
{
bzero(someip,20);
bzero(someport,6);
bzero(sbuf,2048);
//键盘输入要点播的目标客户端ip和端口号
printf("请输入目标ip和端口号!\n");
scanf("%s",someip);
scanf("%s",someport);
printf("请输入要发送的信息!\n");
scanf("%s",truemsg);
//拼接字符串,把信息按照你制定的通信规则发送给服务器
sprintf(sbuf,"p2p@%s@%s@%s",someip,someport,truemsg);
send(tcpsock,sbuf,strlen(sbuf),0);
}
else if(strcmp(sbuf,"all")==0) //表示我想要广播
{
bzero(sbuf,2048);
bzero(broadcastmsg,100);
printf("请输入要广播的信息!\n");
scanf("%s",broadcastmsg);
sprintf(sbuf,"all@%s",broadcastmsg);
printf("%s\n",sbuf);
send(tcpsock,sbuf,strlen(sbuf),0);
}
}
close(tcpsock);
return 0;
}
#include "myhead.h"
/*
服务器的代码
*/
//定义单链表存放目前连接成的所有的客户端信息
struct clientlist
{
//数据域
int sockfd; //套接字
char ipbuf[16]; //ip地址
unsigned short portnum; //端口号
//指针域
struct clientlist *next;
};
struct clientlist *myhead;
//初始化链表头结点
struct clientlist *list_init()
{
struct clientlist *head=malloc(sizeof(struct clientlist));
head->next=NULL;
return head;
}
//尾插结点
int insert_tail(struct clientlist *node,struct clientlist *head)
{
struct clientlist *p=head;
while(p->next!=NULL)
p=p->next;
p->next=node;
}
//专门接收客户端发送过来的信息
void *recvclientmsg(void *arg)
{
struct clientlist *temp=(struct clientlist *)arg;
char rbuf[2048];
char allmsg[2048];
int ret;
while(1)
{
bzero(rbuf,2048);
/*
根据我们制定的通信规则,客户端发过来的信息无非就三种
第一种:getlist 想要所有的客户端信息
第二种:p2p@某个ip@某个端口号@真实信息 想要点播
第三种:all@真实的信息 想要广播给所有的人
//第四种:emoji@表情包的编号
*/
ret=recv(temp->sockfd,rbuf,2048,0);
if(ret==0) //某个客户端断开连接
{
printf("%s:%hu这个客户端断开了!\n",temp->ipbuf,temp->portnum);
//从单链表中删除断开连接的这个客户端(两个指针一前一后配合删除)
struct clientlist *delp1=myhead;
struct clientlist *delp2=myhead->next;
while(delp2!=NULL)
{
if(delp2->sockfd==temp->sockfd) //说明找到了
{
close(temp->sockfd);
delp1->next=delp2->next;
delp2->next=NULL;
free(delp2);
//结束当前线程
pthread_exit(NULL);
}
delp1=delp1->next;
delp2=delp2->next;
}
}
//打印收到的某个客户端的信息
printf("%s:%hu这个客户端发送了信息是:%s\n",temp->ipbuf,temp->portnum,rbuf);
//判断收到的信息
if(strcmp(rbuf,"getlist")==0) //说明这个客户端想让我(服务器)给它发送所有客户端信息
{
//遍历当前存放客户端信息的链表,把客户端的ip和端口号拼接一起发送给对应的客户端
bzero(allmsg,2048);
struct clientlist *p=myhead;
while(p->next!=NULL)
{
p=p->next;
sprintf(allmsg+strlen(allmsg),"%s@%hu\n",p->ipbuf,p->portnum);
}
//把刚才拼接好的信息发送给对应的客户端
send(temp->sockfd,allmsg,strlen(allmsg),0);
}
else //收到的是其他信息(点播/广播的信息)
{
//拆分字符串
char *p=strtok(rbuf,"@");
//判断收到的信息第一个字段
if(strcmp(p,"p2p")==0) //点播
{
//进一步切割,得到对方的ip,端口号,真实信息
char *p1=strtok(NULL,"@");
char *p2=strtok(NULL,"@");
char *p3=strtok(NULL,"@");
//遍历链表找到这个客户端,把p3(真实的信息发送给对方)
struct clientlist *temp1=myhead;
int portn=atoi(p2); //端口号字符串转换成整数
while(temp1->next!=NULL)
{
temp1=temp1->next;
if(strcmp(temp1->ipbuf,p1)==0 && temp1->portnum==portn)
{
bzero(allmsg,2048);
//把发送信息过来的那个客户端的ip和端口号跟p3一起拼接,然后发送给目标客户端
sprintf(allmsg,"从%s:%hu的主机发送过来:%s",temp->ipbuf,temp->portnum,p3);
send(temp1->sockfd,allmsg,strlen(allmsg),0);
}
}
}
else if(strcmp(p,"all")==0) //广播
{
//进一步切割,得到需要广播真实信息
char *p4=strtok(NULL,"@");
//遍历链表把信息发送给所有的人(排除自己)
struct clientlist *temp2=myhead;
while(temp2->next!=NULL)
{
temp2=temp2->next;
if(strcmp(temp2->ipbuf,temp->ipbuf)==0 && temp2->portnum==temp->portnum)
continue; //排除自己
send(temp2->sockfd,p4,strlen(p4),0);
}
}
}
}
}
int main()
{
char sbuf[100];
int tcpsock;
int newsock;
pthread_t id;
int ret;
//定义ipv4地址结构体变量
struct sockaddr_in bindaddr;
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.50"); //女朋友的ip地址
bindaddr.sin_port=htons(20000); //女朋友的端口号
//定义ipv4地址结构体变量存放连接成功的那个客户端ip和端口号
struct sockaddr_in clientaddr;
int addrsize=sizeof(clientaddr);
//初始化链表头结点
myhead=list_init();
//定义属性变量并初始化
pthread_attr_t myattr;
pthread_attr_init(&myattr);
//设置分离属性
pthread_attr_setdetachstate(&myattr,PTHREAD_CREATE_DETACHED);
//创建套接字
tcpsock=socket(AF_INET,SOCK_STREAM,0);
if(tcpsock==-1)
{
perror("创建套接字失败!\n");
return -1;
}
//取消端口号绑定限制
int on=1;
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//绑定ip和端口号
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
if(ret==-1)
{
perror("绑定ip和端口号失败!\n");
return -1;
}
//监听
ret=listen(tcpsock,6);
if(ret==-1)
{
perror("监听失败!\n");
return -1;
}
//不断地接收不同客户端的连接请求
while(1)
{
//接收客户端的连接请求
newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);
if(newsock==-1)
{
perror("接收客户端的连接请求失败!\n");
return -1;
}
printf("新的套接字是:%d\n",newsock);
printf("新连接成功的那个客户端ip是:%s\n",inet_ntoa(clientaddr.sin_addr));
printf("新连接成功的那个客户端端口号是:%hu\n",ntohs(clientaddr.sin_port));
//把连接成功的客户端信息存放到链表
struct clientlist *newnode=malloc(sizeof(struct clientlist));
newnode->sockfd=newsock;
strcpy(newnode->ipbuf,inet_ntoa(clientaddr.sin_addr));
newnode->portnum=ntohs(clientaddr.sin_port);
newnode->next=NULL;
insert_tail(newnode,myhead);
//立马创建一个线程--》专门接收这个客户端发送过来的信息
//每连接成功一个客户端,服务器都有一个专门的线程接收信息,这样子可以并发接收不同客户端同时发送的信息
pthread_create(&id,&myattr,recvclientmsg,newnode);
}
close(tcpsock);
return 0;
}