网络编程二:UDP通信过程+非阻塞IO属性

一、UDP协议

1、UDP协议中由于不需要连接,所以不能使用accept/connect函数。
2、UDP中是如何知道对方的地址???
    在TCP中,先连接,所以connect(对方的IP地址和端口号)/accpet(客户端的IP地址和端口号)
    在UDP中,地址和端口号 这些信号 只能与 数据 一起发送过去,所以接收的时候 地址和端口号 以及数据 一起接收
3、一定要区分 TCP 和 UDP 所使用的接口不一样,不要混淆了    

TCP:  
    客户端:socket(SOCK_STREAM)  (bind)  connect  send  recv  close 
    服务器:socket(SOCK_STREAM)  bind  listen   accept  recv send close 

UDP: 
    客户端: socket(SOCK_DGRAM)  (bind) sendto  recvfrom close 
    服务器: socket(SOCK_DGRAM)  bind recvfrom sendto close

二、UDP实现过程

客户端

1、建立套接字(选择UDP协议)

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int socket(int domain, int type, int protocol);

函数作用:建立套接字

参数: 
    domain:你要选择哪一种地址族 
            PF_INET/AF_INET ------Ipv4 网络协议
            PF_INET6/AF_INET6----- Ipv6 网络协议
    type:  你要选择哪一种 协议(TCP  UDP)
            SOCK_STREAM --流式套接字 TCP 
            SOCK_DGRAM -数据报套接字  UDP 
    protocol:一般设置成 0
    
返回值: 
        成功返回  套接字文件描述符 
        失败  -1

2、绑定自己的IP地址和端口号 

#include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    
参数:    
        sockfd:套接字文件描述符
        addr:自己的IP地址和端口号 
        addrlen:结构体的大小 
        
返回值 :
        成功则返回 0, 
        失败返回-1, 错误原因存于 errno 中
        
struct sockaddr  --旧的结构体
{
    unsigned short sa_family; /*地址族*/
    char sa_data[14];/*14字节的协议地址,包含该socket的IP地址和端口号。*/
};    

//IPV4结构体      
struct sockaddr_in 
{
    short int sin_family; /*地址族  IPV4   IPV6*/
    unsigned short int sin_port; /*端口号*/
    struct in_addr sin_addr; /*IP地址*/
    unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr同样大小*/
};
struct in_addr { 
    in_addr_t s_addr; /*in_addr_t为 32位的unsigned int,该无符号整数采用大端字节序。*/
};    


3、直接发送(聊天)

#include <sys/socket.h>

ssize_t sendto(int socket, const void *message, size_t length,int flags, const struct sockaddr *dest_addr,socklen_t dest_len);

函数作用: 用于UDP中发送数据,注意是UDP

参数: 
        socket :套接字文件描述符 
        message:你要发送的数据 
        length:你要发送的数据大小,注意有多少就发多少 strlen
        flags:一般设置成 0 
        dest_addr:对方的IP地址和端口号
        dest_len:结构体的大小

返回值: 
        成功返回 发送出去的字节数
        失败返回 -1


4、关闭 
    close(socketfd);

服务器端

#include <sys/socket.h>

ssize_t recvfrom(int socket, void * buffer, size_t length,int flags, struct sockaddr * address, socklen_t * address_len);

函数作用:用于UDP中接收数据

参数: 
        socket :套接字文件描述符 
        buffer:接收的数据存储在这里
        length:接收的数据的大小, 以最大的来接收 sizeof()
        flags:一般设置成 0 
        address: 存储 客户端的IP地址和端口号 ,可以获取到是 谁 给你 发送的 
        address_len:结构体的大小

返回值: 
        成功返回 接收到的字节数
        失败返回 -1

三、练习1

修改UDP通信的代码,实现互相聊天

1、UDP发送端.c

#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>


#define  OWNADDR  "192.168.14.3"  //我自己ubuntu的ip地址
#define  OWNPORT  20000 //我自己ubuntu的该程序的端口号

#define  SERVERADDR "192.168.14.3"//服务器的IP地址 也就是  对方的 
#define  SERVERPORT  10000  //服务器的端口号  也就是  对方的 

int main()
{
	//1、建立套接字(选择UDP协议 一定要选择这个 SOCK_DGRAM)
	int socketfd = socket(AF_INET,SOCK_DGRAM,0);
	if(socketfd == -1)
	{
		printf("socketfd error\n");
		return -1;
	}
	//2、绑定自己的IP地址和端口号 ---这一步在客户端 可以省略
	struct sockaddr_in ownAddr;//定义一个IPV4结构体变量,初始化自己的信息
	ownAddr.sin_family = AF_INET;//IPV4
	ownAddr.sin_port = htons(OWNPORT); /*端口号*/
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR);//本地IP-->网络IP
	
	bind(socketfd,(struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//定义一个IPV4结构体变量,存储对方的IP地址和端口号
	struct sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;//IPV4
	serverAddr.sin_port = htons(SERVERPORT); /*端口号*/
	serverAddr.sin_addr.s_addr = inet_addr(SERVERADDR);//本地IP-->网络IP

	
	//3、直接发送(聊天)
	while(1)
	{
		char buf[1024]={0};
		printf("data:");
		scanf("%s",buf);
		
		sendto(socketfd,buf,strlen(buf),0,(struct sockaddr *)&serverAddr,sizeof(struct sockaddr_in));

	}
	
	//4、关闭 
	close(socketfd);
	
	return 0;
}

2、UDP接收端.c

#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>


#define  OWNADDR  "192.168.14.3"  //我自己ubuntu的ip地址
#define  OWNPORT  10000 //我自己ubuntu的该程序的端口号

#define  SERVERADDR "192.168.14.3"//服务器的IP地址 也就是  对方的 
#define  SERVERPORT  10000  //服务器的端口号  也就是  对方的 

int main()
{
	//1、建立套接字(选择UDP协议 一定要选择这个 SOCK_DGRAM)
	int socketfd = socket(AF_INET,SOCK_DGRAM,0);
	if(socketfd == -1)
	{
		printf("socketfd error\n");
		return -1;
	}
	//2、绑定自己的IP地址和端口号 ---这一步在客户端 可以省略
	struct sockaddr_in ownAddr;//定义一个IPV4结构体变量,初始化自己的信息
	ownAddr.sin_family = AF_INET;//IPV4
	ownAddr.sin_port = htons(OWNPORT); /*端口号*/
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR);//本地IP-->网络IP
	
	bind(socketfd,(struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//定义一个IPV4结构体变量,存储对方的IP地址和端口号
	struct sockaddr_in otherAddr;
	int len = sizeof(struct sockaddr_in);//一定要指定大小,作为参数传递进去
	
	//3、直接接收数据(聊天)
	while(1)
	{
		char buf[1024]={0};

		int ret = recvfrom(socketfd,buf,sizeof(buf),0, (struct sockaddr *)&otherAddr,&len);
		if(ret <=0)
			break;
		
		printf("来自[%s]:[%u]:%s\n",inet_ntoa(otherAddr.sin_addr),ntohs(otherAddr.sin_port),buf); //buf: li4给gebi    "hello" + 192.168.14.20
	}
	
	//4、关闭 
	close(socketfd);
	
	return 0;
}

四、练习2

1、UDP发送端+多线程.c

#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
 #include <unistd.h>
#define  OWNADDR  "192.168.14.3"  //我自己ubuntu的ip地址
#define  OWNPORT  20000 //我自己ubuntu的该程序的端口号

#define  SERVERADDR "192.168.14.3"//服务器的IP地址 也就是  对方的 
#define  SERVERPORT  10000  //服务器的端口号  也就是  对方的 


void * start_routine(void *arg)
{
	int socketfd = *((int*)arg);//强转类型 并且解引用 得到socketfd
	//定义一个IPV4结构体变量,存储对方的IP地址和端口号
	struct sockaddr_in otherAddr;
	int len = sizeof(struct sockaddr_in);//一定要指定大小,作为参数传递进去
	
	//接收服务器的数据
	while(1)
	{
		char buf[1024]={0};

		int ret = recvfrom(socketfd,buf,sizeof(buf),0, (struct sockaddr *)&otherAddr,&len);
		if(ret <=0)
			break;
		
		printf("来自[%s]:[%u]:%s\n",inet_ntoa(otherAddr.sin_addr),ntohs(otherAddr.sin_port),buf); //buf: li4给gebi    "hello" + 192.168.14.20
	}
	
	pthread_exit(NULL);
}

int main()
{
	//1、建立套接字(选择UDP协议 一定要选择这个 SOCK_DGRAM)
	int socketfd = socket(AF_INET,SOCK_DGRAM,0);
	if(socketfd == -1)
	{
		printf("socketfd error\n");
		return -1;
	}
	//2、绑定自己的IP地址和端口号 ---这一步在客户端 可以省略
	struct sockaddr_in ownAddr;//定义一个IPV4结构体变量,初始化自己的信息
	ownAddr.sin_family = AF_INET;//IPV4
	ownAddr.sin_port = htons(OWNPORT); /*端口号*/
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR);//本地IP-->网络IP
	
	bind(socketfd,(struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//定义一个IPV4结构体变量,存储对方的IP地址和端口号
	struct sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;//IPV4
	serverAddr.sin_port = htons(SERVERPORT); /*端口号*/
	serverAddr.sin_addr.s_addr = inet_addr(SERVERADDR);//本地IP-->网络IP

	//创建一个子线程,用来接收
	pthread_t thread;
	pthread_create(&thread,NULL,start_routine,&socketfd);
	
	//3、直接发送(聊天)
	while(1)
	{
		char buf[1024]={0};
		printf("data:");
		scanf("%s",buf);
		
		sendto(socketfd,buf,strlen(buf),0,(struct sockaddr *)&serverAddr,sizeof(struct sockaddr_in));

	}
	
	//4、关闭 
	close(socketfd);
	
	return 0;
}

2、UDP接收端+多线程.c

#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
 #include <unistd.h>
 
#define  OWNADDR  "192.168.14.3"  //我自己ubuntu的ip地址 --服务器
#define  OWNPORT  10000 //我自己ubuntu的该程序的端口号  --服务器

#define  OTHERADDR "192.168.14.3"//对方的IP地址  --客户端
#define  OTHERPORT  20000  //对方的端口号   --客户端

void * start_routine(void *arg)
{
	int socketfd = *((int*)arg);//强转类型 并且解引用 得到socketfd
	
	//定义一个IPV4结构体变量,存储对方的IP地址和端口号
	struct sockaddr_in otherAddr;
	otherAddr.sin_family = AF_INET;//IPV4
	otherAddr.sin_port = htons(OTHERPORT); /*端口号*/
	otherAddr.sin_addr.s_addr = inet_addr(OTHERADDR);//本地IP-->网络IP

	//3、直接发送(聊天)
	while(1)
	{
		char buf[1024]={0};
		printf("data:");
		scanf("%s",buf);
		
		sendto(socketfd,buf,strlen(buf),0,(struct sockaddr *)&otherAddr,sizeof(struct sockaddr_in));
	}
	
	pthread_exit(NULL);
}

int main()
{
	//1、建立套接字(选择UDP协议 一定要选择这个 SOCK_DGRAM)
	int socketfd = socket(AF_INET,SOCK_DGRAM,0);
	if(socketfd == -1)
	{
		printf("socketfd error\n");
		return -1;
	}
	//2、绑定自己的IP地址和端口号 ---这一步在客户端 可以省略
	struct sockaddr_in ownAddr;//定义一个IPV4结构体变量,初始化自己的信息
	ownAddr.sin_family = AF_INET;//IPV4
	ownAddr.sin_port = htons(OWNPORT); /*端口号*/
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR);//本地IP-->网络IP
	
	bind(socketfd,(struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//定义一个IPV4结构体变量,存储对方的IP地址和端口号
	struct sockaddr_in otherAddr;
	int len = sizeof(struct sockaddr_in);//一定要指定大小,作为参数传递进去
	
	//创建一个子线程,用来发送数据
	pthread_t thread;
	pthread_create(&thread,NULL,start_routine,&socketfd);
	
	//3、直接接收数据(聊天)
	while(1)
	{
		char buf[1024]={0};

		int ret = recvfrom(socketfd,buf,sizeof(buf),0, (struct sockaddr *)&otherAddr,&len);
		if(ret <=0)
			break;
		
		printf("来自[%s]:[%u]:%s\n",inet_ntoa(otherAddr.sin_addr),ntohs(otherAddr.sin_port),buf); //buf: li4给gebi    "hello" + 192.168.14.20
	}
	
	//4、关闭 
	close(socketfd);
	
	return 0;
}

五、socket编程中四种IO模型

1、阻塞IO(重点)

1)read(fd)  recv(socketfd)  recvfrom(socketfd) ...
    这些函数本身是不具有阻塞属性,而是因为这个文件描述符的本身阻塞属性导致这个函数执行表现出来的效果是阻塞。
2)在默认的情况下,linux中建立的套接字 、文件描述符 都是 阻塞的。

2、非阻塞IO

1)给文件描述符添加非阻塞属性。
2)由于非阻塞属性,所以可以不断地询问套接字中是否有数据到达。

3、多路复用    (重点)

1)同时对多个IO口(文件描述符)进行操作(同时监听多个套接字)
2)可以在规定的时间内检测数据是否到达

4、信号驱动

1)属于异步通信方式
2)当socket中有数据到达时,通过发送信号通知用户

六、阻塞IO

读阻塞:当数据缓冲区中没有数据可以读取时,调用 read  recv recvfrom  scanf    就会无限阻塞。
    写阻塞:当数据缓冲区剩余的大小 小于 写入的数据量(你的空间不够写了),就会发生写阻塞,直到缓冲区的数据被读取。

    例子: int fd = open("xxxx"); --->fd是阻塞。。
                如果使用   O_NONBLOCK  该宏打开的 文件,那么文件描述符是非阻塞。

七、非阻塞IO 

1、阻塞IO 与 非阻塞IO之间的区别

    阻塞IO 
            建立套接字(阻塞)----》读取数据---》判断缓冲区中没有数据 
            
                ————没有数据--》进入一个等待的状态---》直到缓冲区中 有数据为止。
                ---来数据了---》读取数据 --》进入一个等待的状态---》直到缓冲区中 有数据为止。
                
    非阻塞IO 
            建立套接字(阻塞)--》将这个套接字文件描述符的属性设置为非阻塞状态---》读取数据 --》
            
                ---没有数据---》读取失败---》接口会马上返回,不会阻塞
                ---》有数据 ---》读取成功--》接口也会返回 

2、如何给文件描述符设置 非阻塞属性    ---file  control     fcntl

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ... /* arg */ );
            
                
参数: 
        fd :你要设置哪个文件描述符,将这个文件描述符传递进来
        cmd:控制的命令
        arg:由第二个参数进行决定
        
第二个参数cmd:
        

F_GETFL: Get the file status flags and file access modes, 可以得到文件描述符的状态 ,第三个参数可以忽略

 F_SETFL: 设置文件描述符的状态,但只允许 O_APPEND、O_NONBLOCK 和O_ASYNC 位的改变, 其他位的改变将不受影响.

                O_APPEND: 可以设置成 追加 
                O_NONBLOCK:可以设置 非阻塞属性

返回值 : 
        F_GETFL --》返回 文件描述符的状态或者属性
        F_SETFL --> 
                    成功返回 0 
                    失败返回 -1


                    
比如: 
        int socketfd = socket(TCP协议);//默认是阻塞的
        //得到这个套接字文件描述符的属性
        int status = fcntl(socketfd,F_GETFL)
        //将得到的文件描述符的全部属性 中的 其中一个属性设置成 非阻塞 
        status |= O_NONBLOCK;
        fcntl(socketfd,F_SETFL,status);//把变量status的状态设置到文件描述符中

八、例子

设置一个非阻塞属性 给套接字,看看这个套接字 是否 还会阻塞等待客户端的连接。

#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define  OWNADDR  "192.168.14.3"  //我自己电脑的ip地址
#define  OWNPORT  20000 //我自己电脑的该程序的端口号

int main()
{
	printf("当前是服务器 IP:%s Port:%u\n",OWNADDR,OWNPORT);
	//1、买手机(建立套接字)
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if(socketfd == -1)
	{
		printf("没钱了....,失败\n");
		return -1;
	}
	//因为服务器立马退出之后,端口号不会及时释放
	//此时如果服务器又马上运行,那么端口号会被占用,导致服务器分配端口号失败,连接失败
	//所以设置端口号可以复用
	int optval = 1;
	setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
	
	//2、绑定自己的电话号码(绑定自己的IP地址 和端口号)
	//定义一个IPV4结构体变量,初始化自己的IP地址和端口号
	struct sockaddr_in ownAddr;
	ownAddr.sin_family = AF_INET;/*地址族  IPV4*/
	ownAddr.sin_port = htons(OWNPORT); //htons 将本地端口号转为网络端口号
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); //将本地IP地址转为网络IP地址
	
	bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//3、设置铃声(监听) listen
	listen(socketfd,5);
	
	//4、坐等电话(阻塞接收客户端的连接)
	printf("等待客户端连接.......\n");
	//定义一个IPV4结构体变量,存储连接上来的客户端的IP地址 和 端口号 
	struct sockaddr_in clientAddr;
	//如果你要想要获取对方的IP地址和端口号,第三个参数必须把结构体的大小传递进去
	int len = sizeof(struct sockaddr_in);
	
	//给 socketfd设置非阻塞属性 
	int status = fcntl(socketfd,F_GETFL);//得到这个套接字文件描述符的属性
	//将得到的文件描述符的全部属性 中的 其中一个属性设置成 非阻塞 
	status |= O_NONBLOCK;
	int ret = fcntl(socketfd,F_SETFL,status);//把变量status的状态设置到文件描述符中
	if(ret == -1)
	{
		printf("fcntl error\n");

	}
	while(1)
	{
		//此时是非阻塞,会一直不断地循环
		int newClientFd = accept(socketfd,(struct sockaddr*)&clientAddr,&len);
		if(newClientFd != -1)
		{
			//printf("有客户端连接上来了............\n");
			//打印连接上来的客户端的IP地址和端口号,将网络字节序转为 本地字节序
			printf("连接上来的客户端IP:%s 端口号:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
		}
		//printf("11111111111\n");	
	}
	
	//5、关闭 
	close(socketfd);
	//close(newClientFd);
	
	return 0;
}

九、练习

写一个服务器,可以接受多个客户端的连接 ,可以随时接受连接。

        只要连接上服务器的客户端由数据到达的时候,还要把数据打印出来,要求使用非阻塞的方式去实现。

第一种方式: 使用数组 

            struct client{
                int clientfd[100]; //已连接上来的客户端的套接字文件描述符
                int count; //已连接用户的总数
            }
            
第二种方式: 使用链表 
            连接上来一个客户---》新建结点 ---》将客户的套接字文件描述符存储到链表中 
            
            
        
        while(1)
        {
            //此时是非阻塞,会一直不断地循环
            int newClientFd = accept(socketfd,(struct sockaddr*)&clientAddr,&len);
            if(newClientFd != -1)
            {
                //printf("有客户端连接上来了............\n");
                //打印连接上来的客户端的IP地址和端口号,将网络字节序转为 本地字节序
                printf("连接上来的客户端IP:%s 端口号:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
                //连接上来的客户端的套接字文件描述符  添加到 数组  或者 链表 中 
            }
            //看看有没有已连接上来的客户端的套接字有数据变化 ,如果有,那么把数据打印出来
            
        }

/*
练习2:写一个服务器,可以接受多个客户端的连接 ,可以随时接受连接。
		只要连接上服务器的客户端由数据到达的时候,还要把数据打印出来,要求使用非阻塞的方式去实现。
		
第一种方式: 使用数组 

			struct client{
				int clientfd[100]; //已连接上来的客户端的套接字文件描述符
				int count; //已连接用户的总数
			}
*/
#include<stdio.h>
#include <sys/socket.h>
#include <sys/types.h>          /* See NOTES */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>


#define  OWNADDR  "192.168.14.3"  //我自己电脑的ip地址
#define  OWNPORT  20000 //我自己电脑的该程序的端口号

//设计一个结构体数据类型
struct client{
	int clientfd[100]; //已连接上来的客户端的套接字文件描述符
	int count; //已连接用户的总数
};

//定义一个结构体变量,存储和管理连接上来的客户端
struct client clientManager;

void initManager()
{
	//初始化
	int i;
	for(i=0; i<100; i++)
	{
		clientManager.clientfd[i] = -1;//-1代表该位置是空的
	}
	clientManager.count = 0;//刚开始的时候没有客户端连接,那就是0
}

int main()
{
	int i;
	initManager();
	
	printf("服务器的IP地址:%s 端口号:%u\n",OWNADDR,OWNPORT);
	
	//1、买手机(建立套接字)
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if(socketfd == -1)
	{
		printf("没钱了....,失败\n");
		return -1;
	}
	//因为服务器立马退出之后,端口号不会及时释放
	//此时如果服务器又马上运行,那么端口号会被占用,导致服务器分配端口号失败,连接失败
	//所以设置端口号可以复用
	int optval = 1;
	setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
	
	//2、绑定自己的电话号码(绑定自己的IP地址 和端口号)
	//定义一个IPV4结构体变量,初始化自己的IP地址和端口号
	struct sockaddr_in ownAddr;
	ownAddr.sin_family = AF_INET;/*地址族  IPV4*/
	ownAddr.sin_port = htons(OWNPORT); //htons 将本地端口号转为网络端口号
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); //将本地IP地址转为网络IP地址
	
	bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//3、设置铃声(监听) listen
	listen(socketfd,5);
	
	//定义一个IPV4结构体变量,存储连接上来的客户端的IP地址 和 端口号 
	struct sockaddr_in clientAddr;
	//如果你要想要获取对方的IP地址和端口号,第三个参数必须把结构体的大小传递进去
	int len = sizeof(struct sockaddr_in);
	
	//socketfd 设置成非阻塞属性
	int status = fcntl(socketfd,F_GETFL);//得到这个套接字文件描述符的属性
	//将得到的文件描述符的全部属性 中的 其中一个属性设置成 非阻塞 
	status |= O_NONBLOCK;
	fcntl(socketfd,F_SETFL,status);//把变量status的状态设置到文件描述符中

	
	while(1)
	{
		//4、坐等电话(阻塞接收客户端的连接)
		int newClientFd = accept(socketfd,(struct sockaddr*)&clientAddr,&len);
		if(newClientFd != -1)
		{
			printf("有客户端连接上来了............\n");
			//打印连接上来的客户端的IP地址和端口号,将网络字节序转为 本地字节序
			printf("连接上来的客户端IP:%s 端口号:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
			//将已经连接上来的客户端的新的文件描述符 存储到数组中
			
			//newClientFd设置为非阻塞属性
			int status = fcntl(newClientFd,F_GETFL);//得到这个套接字文件描述符的属性
			//将得到的文件描述符的全部属性 中的 其中一个属性设置成 非阻塞 
			status |= O_NONBLOCK;
			fcntl(newClientFd,F_SETFL,status);//把变量status的状态设置到文件描述符中

			clientManager.clientfd[clientManager.count++] = newClientFd;
		}
		//检查已经连接上来的客户端有没有数据发送过来
		for(i=0; i<clientManager.count;i++)
		{
			char buf[1024]={0};
			//重点重点重点:recv 一定是使用 已经连接上来的新客户端的套接字文件描述符 newClientFd
			int ret = recv(clientManager.clientfd[i],buf,sizeof(buf),0);
			if(ret == 0)//代表客户端退出 
			{
				printf("客户端退出.....\n");	
				//如果你是用的链表,那么客户端退出之后,把客户端对应的该结点删除掉
				break;
			}
			else if(ret >0)//有数据过来的时候才打印
			{
				printf("recv:%s\n",buf);
			}
		}
	}

	//5、关闭 
	close(socketfd);
	//close(newClientFd);
	
	return 0;
}

扩展: 
        (使用UDP通信)
        服务器作为 数据的 中转站 负责 转发 数据  
        
        比如 客户端 [li4] 给客户端[laowang] 发送信息"想你"  
        
        思路 : 首先客户端[li4] 给服务器发送数据 :laowang的IP地址和端口号 + 数据  ---》服务器收到之后 解析数据 ,把 laowang的IP地址和端口号 解析出来 ---》服务器 把 数据  转发 给 laowang

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值