IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> socket编程(2) -> 正文阅读

[网络协议]socket编程(2)

多客户端连接同一服务器

一、客户端

#include<stdio.h>
#include<string.h>
#include<unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <netinet/in.h>
//#include <arpa/inet.h>

int main(void)
{
	int tcp_fd=socket(AF_INET,SOCK_STREAM,0);//建立带连接套接字
   	if(tcp_fd<0)
   	{
       printf("socket fail\n");
       return -1;
   	}
   	else
   	{
       printf("socket......\n");
       printf("socket success\n");
   	}
	
	//拨号,服务器的ip+port
	struct sockaddr_in server_addr;
	server_addr.sin_family=AF_INET;
	server_addr.sin_port=htons(43281);//转换程网络字节序
	server_addr.sin_addr.s_addr=inet_addr("192.168.150.136");//将字符串转换成32位的网络字节序
	/*#include<sys/socket.h>
	int connect(int sockfd, const struct sockaddr *servaddr, int *addrlen);
    返回:若成功则返回0,失败则返回-1;
    sockfd是有socket函数返回的套接字描述符,
	第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。
	套接字地址结构必须含有服务器的IP地址和端口号。*/
	int n=connect(tcp_fd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr));
	if(n<0)
	{
		printf("connect failed\n");
		return -2;
	}
    else
    {
        printf("connect\n");
    }
	
	
	//聊天
	char buf[100];
	while(1)
	{
		bzero(buf,100);
		fgets(buf,100,stdin);
		write(tcp_fd,buf,strlen(buf));	//给服务器发送消息
		if(!strncmp(buf,"quit",4))
		{
			break;
		}
	}

	close(tcp_fd);
    return 0;
}

二、服务器

#include<stdio.h>
#include<string.h>
#include<unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <netinet/in.h>
//#include <arpa/inet.h>
#include <pthread.h>

//TCP通信
typedef struct 
{
	struct sockaddr_in client_addr;//存放客户端ip+port
	int con_fd;//存放服务器和客户端通信的通信套接字
}client_addr_table;

void *run (void *arg)
{
	/*1.linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态,
    如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时
    都不会释放线程所占用堆栈和线程描述符(总计8K多)。
    只有当你调用了pthread_join之后这些资源才会被释放。
    若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
    2.unjoinable属性可以在pthread_create时指定,
    或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),
    将状态改为unjoinable状态,确保资源的释放。
    或者将线程置为 joinable,然后适时调用pthread_join.
    3.其实简单的说就是在线程函数头加上 pthread_detach(pthread_self())的话,
    线程状态改变,在函数尾部直接 pthread_exit线程就会自动退出。省去了给线程擦屁股的麻烦。

    pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源
    */
    pthread_detach(pthread_self());//分离当前线程,就不用主线程去结合
	client_addr_table *p=(client_addr_table *)arg;
	//聊天
	char buf[100];
	while(1)
	{
		bzero(buf,100);
		read(p->con_fd,buf,100);
		if(!strncmp(buf,"quit",4))
		{
			break;
		}
		printf("[%s][%d]:%s\n",
            inet_ntoa(p->client_addr.sin_addr),ntohs(p->client_addr.sin_port),buf);
	}
	close(p->con_fd);
	printf("[%s][%d]客户端退出\n",
        inet_ntoa(p->client_addr.sin_addr),ntohs(p->client_addr.sin_port));
}

int main(void)
{
    /*
    #include <sys/types.h>
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
    返回值:非负描述符 – 成功,-1 - 出错
    1) domain为地址族(Address Family),也就是 IP 地址类型,
    常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。
    AF_INET 表示 IPv4 地址,例如 127.0.0.1;
    AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
    127.0.0.1,它是一个特殊IP地址,表示本机地址。
    2) type 为数据传输方式/套接字类型,
    常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 
    和 SOCK_DGRAM(数据报套接字/无连接的套接字)
    3) protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,
    分别表示 TCP 传输协议和 UDP 传输协议。
    一般情况下有了 af 和 type 两个参数就可以创建套接字了,
    操作系统会自动推演出协议类型,除非遇到这样的情况:
    有两种不同的协议支持同一种地址类型和数据传输类型。
    如果我们不指明使用哪种协议,操作系统是没办法自动推演的。
    */
   //建立套接字
   int tcp_fd=socket(AF_INET,SOCK_STREAM,0);
   if(tcp_fd<0)
   {
       printf("socket fail\n");
       return -1;
   }
   else
   {
       printf("socket......\n");
       printf("socket success\n");
   }

   /*struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。
    一、sockaddr
    sockaddr在头文件#include <sys/socket.h>中定义,
    sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起
    struct sockaddr 
    {  
        sa_family_t sin_family;//地址族
      char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   }; 
    二、sockaddr_in
    sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,
    该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
    struct sockaddr_in
    {
        short sin_family;
        //Address family一般来说AF_INET(地址族)PF_INET(协议族
 
        unsigned short sin_port;
        //Port number(必须要采用网络数据格式,
        //普通数字可以用htons()函数转换成网络数据格式的数字)
 
        struct in_addr sin_addr;
        //IP address in network byte order(Internet address)
 
        unsigned char sin_zero[8];
        //Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐
 
    };

    //存放32IP地址
    typedef uint32_t in_addr_t;
    struct in_addr
    {
        in_addr_t s_addr;
    };
    */
   	struct sockaddr_in server_addr;
	server_addr.sin_family=AF_INET;
    /*htons 转换程网络字节序
    #include <arpa/inet.h> 
    uint16_t htons(uint16_t hostshort); 
    将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian),电脑存储小端序

    https://www.zhihu.com/question/54580649/answer/145500997
    端口的作用:
    我们知道一台主机(对应一个IP地址)可以提供很多服务,比如web服务,ftp服务等等。
    如果只有一个IP,无法却分不同的网络服务,所以我们采用”IP+端口号”来区分不同的网络服务。 
    端口的定义:
    端口号是标识主机内唯一的一个进程,IP+端口号就可以标识网络中的唯一进程。
    在我们通常用的Socket编程中,IP+端口号就是套接字 
    端口号是由16比特进行编号,范围是0-65535,按照道理来讲,这些端口你都可以随便用。
    但是你不是vip用户,所以有一些端口被vip用户占着。
    比如FTP 21 Ssh 22等等,所以给端口分了类,规定你可以使用端口的范围。 
    端口的分类
    分类的维度很多,这里我们按照服务端使用还是客户端使用分类 
    a.服务端使用的端口号 预留端口号        
    取值范围0-1023,这些端口我们编程的时候不能使用,是那些vip应用程序使用的,
    只有超级用户特权的应用才允许被分配一个预留端口号 
    登记端口号        
    取值范围1024-49151,就是我们平时编写服务器使用的端口号范围 ,
    服务器的端口号,只要在这个范围就好.
    客户端使用的端口号    
    取值范围49152-65535,这部分是客户端进程运行时动态选择的范围,又叫临时端口号*/
	server_addr.sin_port=htons(43281);
    /* #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>
    将字符串转换成32位的网络字节序
    char *inet_ntoa(struct in_addr in);
    tcp_addr.sin_addr.s_addr=inet_addr(INADDR_ANY)
    宏INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,
    因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
    就比方说我这里本机的ip地址包括:
    192.168.150.136   ifconfig
    127.0.0.1
    0.0.0.0
    */
	server_addr.sin_addr.s_addr=inet_addr("192.168.150.136");

    /*bind函数把一个本地协议地址赋予一个套接字。
    #include<sys/socket.h>
    int bind(int sockfd,  const struct sockaddr, socklen_t addrlen);
    第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。*/
    bind(tcp_fd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr));

    /*头文件:#include <sys/socket.h>
    定义函数:int listen(int s, int backlog);
    函数说明:listen()用来等待参数s 的socket 连线. 
    参数backlog 指定同时能处理的最大连接要求, 
    如果连接数目达此上限则client 端将收到ECONNREFUSED 的错误. 
    Listen()并未开始接收连线, 只是设置socket 为listen 模式, 
    真正接收client 端连线的是accept(). 
    通常listen()会在socket(), bind()之后调用, 接着才调用accept().
    */
	listen(tcp_fd,4);

    int con_fd;
    struct sockaddr_in client_addr;
    socklen_t client_length=sizeof(struct sockaddr);//存放客户端结构体地址大小的变量  
    int i=0;
    client_addr_table client_table[100];


	while(1)
	{
        /*当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
        int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);*/
        con_fd=accept(tcp_fd,(struct sockaddr *)&client_addr,&client_length);//建立通信套接字
        if(con_fd<0)
        {
            printf("accept failed\n");
            return -2;
        }
        else
        {
            printf("connect\n");
        }
        
        client_table[i].client_addr=client_addr;
		client_table[i].con_fd=con_fd;
        pthread_t tcp_thread;
		printf("[%s][%d]客户端连接成功\n",
            inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
		/*函数简介
        pthread_create是UNIX环境创建线程函数
        在默认情况下通过pthread_create函数创建的线程是非分离属性的,
        由pthread_create函数的第二个参数决定,在非分离的情况下,
        当一个线程结束的时候,它所占用的系统资源并没有完全真正的释放,也没有真正终止。
        头文件
        #include<pthread.h>
        函数声明
        int pthread_create(pthread_t *restrict tidp,
            const pthread_attr_t *restrict_attr,
            void*(*start_rtn)(void*),
            void *restrict arg);
        返回值 若成功则返回0,否则返回出错编号
        参数
        第一个参数为指向线程标识符的指针。
        第二个参数用来设置线程属性。
        第三个参数是线程运行函数的地址。
        最后一个参数是运行函数的参数。
        */
        int ret=pthread_create(&tcp_thread, NULL,run, (void *)&client_table[i]);
        if(ret != 0)
        {
            printf("creat pthread fail\n");
        }
        /*创建线程分离属性
        pthread_attr_t ta;
        pthread_attr_init(&ta);
	    //设置线程为分离状态
        pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED); 
        ret = pthread_create(&ivi_rxtid, &ta, (void *)run,(void *)&client_table[i]);
        if (ret != 0)//创建失败
        {
            printf("creat pthread fail\n");
        }*/
		i++;
	}

	close(con_fd);
	close(tcp_fd);

    return 0;
}

?三、效果

服务器

?

客户端

?

?

?

?

?

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-04 11:34:02  更:2021-08-04 11:35:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 18:55:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码