精华内容
下载资源
问答
  • 聊天室——17年暑假项目(Linux C网络编程)-源码
  • Linux网络编程(第2版)》涉及面广,从基本的编程工具介绍和编程环境搭建,到高级技术和核心原理,再到项目实战,几乎涉及Linux网络编程的所有重要知识。 《Linux网络编程(第2版)》共分4篇。 第1篇介绍Linux操作...
  • Linux网络编程小例子

    2010-05-11 23:03:57
    Linux网络编程实例,自己编写一个HTTP协议的目录浏览和文件下载服务器,网络搜集,向作者的开源精神致敬!!!
  • LINUX网络编程项目

    2012-09-20 10:28:35
    LINUX网络编程项目 TCP UDP 多线程聊天
  • linux网络编程 c语言实现 聊天程序,能够接入多个客户,实现并发聊天
  • 也怪不得很多招聘要求是写要多线程编程,网络编程,可能实际就是去弄服务器编程开发。 面向服务器的应用开发一直是Linux软件开发领域的重点,而Linux高性能服务器应用的开发则是重点中的难点。---《Linux高性能...

    我觉得这个说得比较好,不在于项目有多大,有多难,在于项目是否是自己真正做的,自己是否完全消化了。

    https://blog.51cto.com/14419148/2416408

    https://blog.csdn.net/trb331617/article/details/79275091

    https://blog.csdn.net/trb331617/article/details/79247407

    如果linux c不好弄些项目的话,我们可以多做些C++的项目,我觉得C++的项目应该蛮多的,可以拿来练手,就像JAVA,应该是有不少项目可以弄弄的!

    这本书里面也有一些实战项目我感觉不错,可以看看。

    https://gitbook.cn/gitchat/column/5d11e726820bf61799b8277f

    范蠡 · 资深开发工程师

    王道是弄个百度网盘的项目

    不过好像也有一个类似于华清的词典的项目

    这个专门教linux后台开发的,最后的项目居然也是网盘,这个真的是系统教Linux  c/C++后台开发的

    https://ke.qq.com/course/417774?taid=9141958149038062

    还有一种你不知道做什么项目,你可以去看一些牛人的自我介绍,他做过什么项目,其实能够看到别人的简历最好的额,但是你看不懂别人的简历,你可以去网上找一些牛人或者一些linux c书的作者,可以看到他介绍自己做过什么系统。我觉得这是一个不错的方法。

    下面这个也做过直播服务器,要不我也先自己做个直播服务器先?

    https://cloud.tencent.com/developer/article/1501953

     https://ke.qq.com/course/417774?taid=3659514000072686

    =============================================================================================

    有的招聘要求是写有设计大型并发服务器的能力 ,我也意识到很多Linux c可能是去做服务器编程了,我想起之前一本书叫《linux高性能服务器编程》我去里面找找有没有这方面的项目还真有!!!!!

    还有一本书《linux多线程服务端编程》也可以看看

    我现在越发感觉到linux应用层编程,也就是多线程编程,网路编程,实际工作里面似乎就是服务器编程,服务器端的开发。所以你弄项目可以弄一个服务器端的项目,也怪不得之前没有找到合适的Linux c的项目,因为你没有弄清楚实际开发里面Linux c编程是用在什么地方。

    也怪不得很多招聘要求是写要多线程编程,网络编程,可能实际就是去弄服务器编程开发。

    面向服务器的应用开发一直是Linux软件开发领域的重点,而Linux高性能服务器应用的开发则是重点中的难点。---《Linux高性能服务器编程》

    去搜搜服务器编程方面的书和资料。

     作者专门提供了一个负载均衡服务器程序,

    这样就有助于我弄出一个企业级的项目出来,而不是一点经验也没有,

    ==============================================================================================

    我发现一个特点,不少服务器开发是和流媒体相关的

    零声的linux服务器高级架构师的课程里面也有专门讲流媒体的,之前看很多人的简介也是做流媒体项目的,还有很多招聘要求也是的。

     

    零声弄的实战项目也是流媒体的实战项目,所以我在想这个流媒体是不是又是服务器里面的一个重点呢?可能本身音频视频现在用得很广,哪个网站离不开一点音频视频什么的。再加上现在直播这么火。所以现在的网站可能不是仅仅一点文字信息,你从这个角度考虑可能就比较好理解了。这样我可能对服务器编程看得更清一点了。毕竟现在4G 5G,可能视频应用会更多。

     

    展开全文
  • 项目是基于Linux下的网络编程的一个扩展项目,基于网络的聊天工具,项目模块包括 Linux C 服务器, Linux C 客户端及数据库的制作。服务器采用 TCP 线并发服务器来实现多个客户端同时连接并占用服务器的情况,其中...
  • linux网络编程的几个简单例子

    千次阅读 2019-07-09 15:00:19
    TCP连接的多进程收发 1)... if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))//将输入的ip地址转化成网络字节序 { printf("invalid server_ip\n"); return -1; } memset(tSocketServerAddr.sin_zero, 0...

    TCP连接的多进程收发

    1)服务器端

    1.需要先获得socket

    fd = socket(AF_INET, SOCK_DGRAM, 0);

    AF_INET代表使用IPV4协议族

    SOCK_DGRAM代表TCP/IP连接

     

    2.bind()  把获得的socket和要监听的ip端口绑定起来

    3.listen()启动监测数据

    4.accept()接受/建立一条连接 等待客户端连接

    5.send() 发数据

    recv() 收数据

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h>
    
    
    /* socket
     * bind
     * listen
     * accept
     * send/recv
     */
    
    #define SERVER_PORT 7897
    #define BACKLOG     10
    
    int main(int argc, char **argv)
    {
    	int iSocketServer;
    	int iSocketClient;
    	struct sockaddr_in tSocketServerAddr; //指定服务器绑定地址
    	struct sockaddr_in tSocketClientAddr; //保存客户端地址
    	int iRet;
    	int iAddrLen;
    
    	int iRecvLen;
    	unsigned char ucRecvBuf[1000];
    
    	int iClientNum = -1;
    
    	signal(SIGCHLD,SIG_IGN); //此函数用于处理僵尸进程
    	
    	iSocketServer = socket(AF_INET, SOCK_STREAM, 0);//AF_INET IPV4连接 SOCK_STREAM启动TCP连接
    	if (-1 == iSocketServer)
    	{
    		printf("socket error!\n");
    		return -1;
    	}
    
    	tSocketServerAddr.sin_family      = AF_INET; //一般设置为
    	tSocketServerAddr.sin_port        = htons(SERVER_PORT); //将SERVER_PORT转化为网络字节序 host to net, short
     	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY表示本机上所有IP
    	memset(tSocketServerAddr.sin_zero, 0, 8);
    
    
    	int opt = 1;  
        //使用setsockopt函数可以保证端口可被重复绑定
        iRet = setsockopt(iSocketServer, SOL_SOCKET,SO_REUSEADDR,   
        				(const void *)&opt, sizeof(opt) );	
    	if (-1 == iRet)
    	{
    		printf("set sock option error!\n");
    		close(iSocketServer);
    		return -1;
    	}
    	
    	
    	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); //绑定端口
    	if (-1 == iRet)
    	{
    		printf("bind error!\n");
    		close(iSocketServer);
    		return -1;
    	}
    
    	iRet = listen(iSocketServer, BACKLOG); //设置监听 BACKLOG代表同时监听10路连接
    	if (-1 == iRet)
    	{
    		printf("listen error!\n");
    		return -1;
    	}
    
    	while (1)
    	{
    		iAddrLen = sizeof(struct sockaddr);
    		iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen); //等待连接 如果建立连接
    		if (-1 != iSocketClient)
    		{
    			iClientNum++;
    			printf("Get connect from client %d : %s\n",  iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
    			if (!fork()) //fork创建子进程 返回值为0的是子进程 
    			{
    				/* 子进程中处理客户端数据 */
    				while (1)
    				{
    					/* 接收客户端发来的数据并显示出来 */
    					iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
    					if (iRecvLen <= 0)
    					{
    						close(iSocketClient); //关闭连接
    						return -1;
    					}
    					else
    					{
    						ucRecvBuf[iRecvLen] = '\0';
    						printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
    						send(iSocketClient, "ok", 2, 0);//打印的同时发送一条数据给连接的客户端
    					}
    				}				
    			}
    		}
    	}
    	
    	close(iSocketServer);
    	return 0;
    }
    
    
    

    服务器端每收到一个客户端连接都会使用fork()创建一个子进程。每个子进程分别处理自己的数据请求。

    关于fork()函数的详解可以看看这篇文章https://blog.csdn.net/weixin_42736024/article/details/81412962

     

    2)客户端

    1、获取socket

    iSocketClient = socket(AF_INET, SOCK_STREAM, 0);

    AF_INET代表使用IPV4协议族

    SOCK_STREAM代表tcp/ip连接

    2、将获取的ip 使用inet_aton()转化为能用的ip

    3、将socket链接到指定端口

    connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));

    3、send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0); 发送数据

     

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    
    /* socket
     * connect
     * send/recv
     */
    
    #define SERVER_PORT 8888
    
    int main(int argc, char **argv)
    {
    	int iSocketClient;
    	struct sockaddr_in tSocketServerAddr;
    	
    	int iRet;
    	unsigned char ucSendBuf[1000];
    	int iSendLen;
    
    	if (argc != 2)
    	{
    		printf("Usage:\n");
    		printf("%s <server_ip>\n", argv[0]);
    		return -1;
    	}
    
    	iSocketClient = socket(AF_INET, SOCK_STREAM, 0); //获得socket
    
        //设置需要连接的ip端口
    	tSocketServerAddr.sin_family      = AF_INET;
    	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /*将端口转化成网络字节序 */
     	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
     	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr)) //将命令行读入的字符串ip转化成能用的ip
     	{
    		printf("invalid server_ip\n");
    		return -1;
    	}
    	memset(tSocketServerAddr.sin_zero, 0, 8);
    
    
    	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));//建立连接
    	if (-1 == iRet)
    	{
    		printf("connect error!\n");
    		return -1;
    	}
    
    	while (1)
    	{
    		if (fgets(ucSendBuf, 999, stdin))//从标准输入获得数据 以回车为结尾
    		{
    			iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
    			if (iSendLen <= 0)
    			{
    				close(iSocketClient);
    				return -1;
    			}
    		}
    	}
    	
    	return 0;
    }
    
    

     

     

    UDP连接

    1)服务器

    fd=socket()

    bind()绑定IP端口

    revfrom()接收数据

    sendto()发送数据

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h>
    
    
    /* socket
     * bind
     * sendto/recvfrom
     */
    
    #define SERVER_PORT 8888
    
    int main(int argc, char **argv)
    {
    	int iSocketServer;
    	int iSocketClient;
    	struct sockaddr_in tSocketServerAddr;
    	struct sockaddr_in tSocketClientAddr;
    	int iRet;
    	int iAddrLen;
    
    	int iRecvLen;
    	unsigned char ucRecvBuf[1000];
    
    	int iClientNum = -1;
    	
    	iSocketServer = socket(AF_INET, SOCK_DGRAM, 0); //SOCK_DGRAM代表UDP连接
    	if (-1 == iSocketServer)
    	{
    		printf("socket error!\n");
    		return -1;
    	}
    
    	tSocketServerAddr.sin_family      = AF_INET;
    	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
     	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    	memset(tSocketServerAddr.sin_zero, 0, 8);
    	
    	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
    	if (-1 == iRet)
    	{
    		printf("bind error!\n");
    		return -1;
    	}
    
    
    	while (1)
    	{
    		iAddrLen = sizeof(struct sockaddr);
    		iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
    		if (iRecvLen > 0)
    		{
    			ucRecvBuf[iRecvLen] = '\0';
    			printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
    		}
    	}
    	
    	close(iSocketServer);
    	return 0;
    }
    
    
    

    2)客户端

    fd=socket

    connect()    UDP连接的connect实际上不会进行连接只会绑定IP端口

    sendto()或者send()发送数据

    recvform() 或者recv()接收数据

     

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    
    /* socket
     * connect
     * send/recv
     */
    
    #define SERVER_PORT 8888
    
    int main(int argc, char **argv)
    {
    	int iSocketClient;
    	struct sockaddr_in tSocketServerAddr;
    	
    	int iRet;
    	unsigned char ucSendBuf[1000];
    	int iSendLen;
    
    	if (argc != 2)
    	{
    		printf("Usage:\n");
    		printf("%s <server_ip>\n", argv[0]);
    		return -1;
    	}
    
    	iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
    
    	tSocketServerAddr.sin_family      = AF_INET;
    	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
     	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
     	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
     	{
    		printf("invalid server_ip\n");
    		return -1;
    	}
    	memset(tSocketServerAddr.sin_zero, 0, 8);
    
    
    	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
    	if (-1 == iRet)
    	{
    		printf("connect error!\n");
    		return -1;
    	}
    
    	while (1)
    	{
    		if (fgets(ucSendBuf, 999, stdin))
    		{
    			iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
    			if (iSendLen <= 0)
    			{
    				close(iSocketClient);
    				return -1;
    			}
    		}
    	}
    	
    	return 0;
    }
    
    

     

     

    UDP服务器多线程收发

    1)服务器

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h>
    #include <pthread.h>
    
    
    /* socket
     * bind
     * sendto/recvfrom
     */
    
    #define SERVER_PORT 5678
    
    static pthread_t g_tSendTreadID;
    static pthread_t g_tRecvTreadID;
    
    
    static pthread_mutex_t g_tSendMutex  = PTHREAD_MUTEX_INITIALIZER;
    static pthread_cond_t  g_tSendConVar = PTHREAD_COND_INITIALIZER;
    
    static struct sockaddr_in tSocketServerAddr;
    static struct sockaddr_in tSocketClientAddr;
    
    int iSocketServer;
    int iSocketClient;
    
    
    static void *SendTreadFunction(void *pVoid)
    {
       int iAddrLen = sizeof(struct sockaddr);
      while(1)
      {
    
        /* 平时休眠 */
    	pthread_mutex_lock(&g_tSendMutex);
    	pthread_cond_wait(&g_tSendConVar, &g_tSendMutex);	
    	pthread_mutex_unlock(&g_tSendMutex);
    
        sendto(iSocketServer, "ok\n", 2, 0,(const struct sockaddr *)&tSocketClientAddr, iAddrLen);
       
      }
    }
    
    int main(int argc, char **argv)
    {
    
    	int iRet;
    	int iAddrLen;
    
    	int iRecvLen;
    	unsigned char ucRecvBuf[1000];
    
    	int iClientNum = -1;
    
    	iSocketServer = socket(AF_INET, SOCK_DGRAM, 0); //获得socket
    	if (-1 == iSocketServer)
    	{
    		printf("socket error!\n");
    		return -1;
    	}
    
    	tSocketServerAddr.sin_family      = AF_INET;
    	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
     	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    	memset(tSocketServerAddr.sin_zero, 0, 8);
    
    	int opt = 1;  
        iRet = setsockopt(iSocketServer, SOL_SOCKET,SO_REUSEADDR,   
        				(const void *)&opt, sizeof(opt) );	//使用此函数可以保证端口可被重复绑定
    	if (-1 == iRet)
    	{
    		printf("set sock option error!\n");
    		close(iSocketServer);
    		return -1;
    	}
    	
    	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); //将socket与ip端口绑定在一起
    	if (-1 == iRet)
    	{
    		printf("bind error!\n");
    		close(iSocketServer);
    		return -1;
    	}
    
        /* 创建netprint发送线程: 它用来发送打印信息给客户端 */
    	pthread_create(&g_tSendTreadID, NULL, SendTreadFunction, NULL);	
    	//pthread_create(&g_tRecvTreadID, NULL, RecvTreadFunction, NULL);
    
    	while (1)
    	{
    		iAddrLen = sizeof(struct sockaddr);
    		iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
    		if (iRecvLen > 0)
    		{
    			ucRecvBuf[iRecvLen] = '\0';
    			printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
    
                //唤醒发送线程
    			pthread_mutex_lock(&g_tSendMutex);
    			pthread_cond_signal(&g_tSendConVar);
    			pthread_mutex_unlock(&g_tSendMutex);	
    		}
    	}
    	
    	close(iSocketServer);
    	return 0;
    }
    
    
    

    2)客户端

    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    
    /* socket
     * connect
     * send/recv
     */
    
    #define SERVER_PORT 5678
    
    int main(int argc, char **argv)
    {
    	int iSocketClient;
    	struct sockaddr_in tSocketServerAddr;
    	struct sockaddr_in tSocketClientAddr;
    	int iRet;
    	int iRecvLen;
    	unsigned char ucSendBuf[1000];
    	unsigned char ucRecvBuf[1000];
    	int iSendLen;
    	int iAddrLen;
    
    	if (argc != 2)
    	{
    		printf("Usage:\n");
    		printf("%s <server_ip>\n", argv[0]); //
    		return -1;
    	}
    
    	iSocketClient = socket(AF_INET, SOCK_DGRAM, 0); //申请socket
    
    	tSocketServerAddr.sin_family      = AF_INET;
    	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
     	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
     	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))//将输入的ip地址转化成网络字节序
     	{
    		printf("invalid server_ip\n");
    		return -1;
    	}
    	memset(tSocketServerAddr.sin_zero, 0, 8);
    
    
    
    	while (1)
    	{
    		if (fgets(ucSendBuf, 999, stdin))
    		{
    
    			iAddrLen = sizeof(struct sockaddr);
    			iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,
    			                      (const struct sockaddr *)&tSocketServerAddr, iAddrLen);
    			
    			if (iSendLen <= 0)
    			{
    				close(iSocketClient);
    				return -1;
    			}
    		}
    
    
    		iAddrLen = sizeof(struct sockaddr);
    		//收到来自 inet_ntoa(tSocketClientAddr.sin_addr)     地址的信息
    		iRecvLen = recvfrom(iSocketClient, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen); 
    
    		if (iRecvLen > 0)	
    		{
    
               printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
    		}
    
    		
    	}
    	
    	return 0;
    }
    
    

     

    展开全文
  • linux网络编程-多进程并发
  • PAGE PAGE 3 项目7 Shell编程 项目指导书 一实训目的 掌握Shell环境变量管道输入输出重定向的使用方法 熟悉Shell程序设计 二项目背景 某单位的系统管理员计划用Shell编程编写一个程序实现USB设备的自动挂载程序的...
  • 本阶段为Linux系统编程课程,理解Linux操作系统一切皆文件的含义,理解多进程多线的实现细节,理解网络通信原理,掌握TCP\UDP编程原理和步骤,理解上层应用程序如何通过Linux操作系统访问底层硬件。本阶段的项目是...
  • linux网络编程(完整版)

    万次阅读 多人点赞 2019-03-29 17:02:46
    之间在网上看到很多网络编程都是一个一个demo,今天我把之前学到的汇总起来,希望大家可以进行补充。 线程中我使用过两种方式编程,一种是经典函数式编程加上标志位,如下: while(1) { server_init(); ...

    之间在网上看到很多网络编程都是一个一个demo,今天我把之前学到的汇总起来,希望大家可以进行补充。
    我理解的网络通信分为4种
    1,udp客户端
    2,udp服务端
    3,tcp客户端
    4,tcp服务端

    线程中我使用过两种方式编程,一种是经典函数式编程加上标志位,如下:

    
    while(1)
    {
    		server_init();
    		client_init();
    		sock_send();
    		select_handler();
    }
    
    
    

    其中各函数里面放置了大量的标志位,如下:

    void client_init(void)
    {
    	  //确认客户端初始化标志位
    	  //
    }
    void sock_send(void)
    {
    		//判断客户端标志位,成功则继续进行
    		if(client_init_flag)
    		{
    				//发送操作
    				//确认客户端发送标志位
    		}
    }
    
    void select_handler(void)
    {
    		if(send_flag)
    		{
    				//处理数据并接受可以用select
    		}
    }
    

    这种方式,我觉得在看代码的时候很乱,但是他在大量的通信时还比较友好,可以建立一个结构体数组,每个数组成员代表一个客户端,结构体放置client_init_flag和send_flag。
    还有一种方式采用的是状态机编程

    创建枚举
    typedef enum client_statues_t
    {
    		init_flag_status,
    		send_flag_status,
    }client_statues_t;
    client_statues_t client_statues;
    
    while(1)
    {
    		//先接受,后发送
    		switch(client_statues)
    		{
    			 case init_flag_status:
    			 		client_init();
    			 		break;
    			 case send_flag_status:
    			 		sock_send();
    			 		break; 	
    			 。。。
    			 	
    		}
    
    }
    

    状态机在单片机使用很常见,但是如果多客户端初始化与发送,容易搞混,并且个人觉得增删查改略费劲,有可能是我自己水平有限,所以今天只写了一个关于第一种方式的代码。因为标志位太多了,而且各种判断比较乱,所以以下就没有各种标志位,但真正项目中是要有的并且还需要有打印日志功能(printf函数),整体思路如下:

    >>创建udp服务端,创建tcp服务端
    >>创建udp客户端,创建tcp客户端
    			>>发送数据
    						>>接受数据并处理
    			
    

    首先是udp服务端,创建tcp服务端,服务端程序比客户端较简单

    //返回值为是否成功标志,需要在各行赋值代码中判断,此代码不进行演示
    //create_udpServer()、create_tcpServer()参数可以为全局变量的关于服务器端的结构体,结构体里端口号,初始化标志位,等等,此代码不进行演示
    
    struct sockaddr_in udp_sockServer;
    void create_udpServer(void)
    {
    		udp_socket = socket(AF_INET, SOCK_DGRAM,0);
    		//INADDR_ANY,
    		udp_sockServer.sin_addr.s_addr = htonl(INADDR_ANY);
    		udp_sockServer.sin_port = htons(udp_port);//port自己设置
    		udp_sockServer.sin_family = AF_INET;
    		bind(udp_socket, (struct sockaddr *)&udp_sockServer, sizeof(struct sockaddr_in));
    
    }
    struct sockaddr_in tcp_sockServer;
    void create_tcpServer(void)
    {
    		tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
    		tcp_sockServer.sin_addr.s_addr = htonl(INADDR_ANY);
    		tcp_sockServer.sin_port = htons(tcp_podt);//port自己设置
    		tcp_sockServer.sin_family = AF_INET;
    		bind(tcp_socket, (struct sockaddr *)&tcp_sockServer, sizeof(struct sockaddr_in));
    		listen(tcp_socket, n);//n自己设置
    }
    void server_init(void)
    {
    		create_udpServer();
    		create_tcpServer();
    }
    

    其次是创建tcp和udp的客户端

    //返回值为是否成功标志,需要在各行赋值代码中判断,此代码不进行演示
    //create_tcpClient()、create_udpClient(v)参数可以为全局变量的关于客户端的结构体,要连接服务器的结构体信息,初始化标志位,等等,此代码不进行演示
    struct sockaddr_in  tcpServer;
    void create_tcpClient(void)
    {
    		tcpSock = socket(AF_INET, SOCK_STREAM, 0);
    		//客户端需要知道服务端信息
    		tcpServer.sin_addr.s_addr = htonl(ip);//ip自己设置
    		tcpServer.sin_port = htons(tcpPort);//port自己设置
    		tcpServer.sin_family = AF_INET;
     	   /*	设置setsockopt参数 ,具体请看我之间发的一篇blog
    		*https://blog.csdn.net/qq_32166779/article/details/88853435
    		*/
    		
    }
    /*把connect单独写是因为这个步骤比较特殊,他是阻塞函数,需要有一定的延时,有两种方、法,一种是利用setsockopt:https://blog.csdn.net/qq_32166779/article/details/88853435
    一种是利用select检查socket描述符:http://blog.csdn.net/ast_224/article/details/2957294
    */
    void tcpClient_connectServer(void)
    {
            //这个tcpsock和create_tcpClient函数中是同一个
    		connect(tcpSock, (struct sockaddr *)&tcpServer, sizeof(struct sockaddr_in));
    }
    
    struct sockaddr_in  udpServer;
    void create_udpClient(void)
    {
    		udpSock = socket(AF_INET, SOCK_DGRAM, 0);
    		udpServer.sin_addr.s_addr = htonl(pUDP->ip);
    	    udpServer.sin_port = htons(pUDP->udpPort);
    	    udpServer.sin_family = AF_INET;
    }
    
    void client_init(void)
    {
    		create_tcpClient();
    		tcpClient_connectServer();
    		create_udpClient();
    }
    
    
    

    udp 服务器接受与发送函数,这个只设置能接受一个客户端发数

    //udp先接受客户端的数据,然后发送数据
    void udp_server_recviveandsend(void)
    {
    				/*
    		参数udp_socket为 建立udp服务端的
    		*/
    		recvfrom(udp_socket, rxBuf, rxbuf_len, 0,(struct sockaddr *)&sockClient, &(sizeof(struct sockaddr_in)));
    		
    		sendto(udp_socket , txBuf, txbuf_len, 0, (struct sockaddr *)&sockClient,sizeof(struct      sockaddr_in));
    
    }
     
    

    //tcp客户端发数与接受

    void udp_server_recviveandsend(void)
    {
    	    /*tcpSock为建立*/
    		send(tcpSock , txbuf, len, 0);
    		recv(tcpSock , rxbuf, len, 0);
    		
    }
    

    //tcp服务端是最难的,需要考虑客户端的ip,个数限制,并根据每个客户端进行通信,这里需要设置一个结构体数组,

    typedef struct tcp_accept_t
    {
    	int lifeNum;//用来设置socket存在时间
    	int socket;//
    	ulong ip;  //保存客户端ip
    	ushort port;//保存客户端端口号
    	int len;
    }tcp_accept_t;
    #define num 100//定义最大接受客户端的数量,
    tcp_accept_t tcp_accept[num];
    
    

    tcp接受客户端大体思路是这样想的:
    1,利用select先获取accept之前的套接字
    2,当accept响应,建立新套接字
    3,有了新套接字,利用select获取receive的响应
    4,如果新来客户端总数超过num,则放弃最早的客户端

     create_tcpServer();
     struct sockaddr_in sockClient;
    while(1)
    {
    		/*先*/
    		int  		socketMax=-1;
    		fd_set 	fdSockSet;
    		int    		fd_act;
    		struct timeval timeout;
    
    		FD_ZERO(&fdSockSet);
    		//tcp_sockServer与create_tcpServer()一致
    		if(tcp_sockServer>0)
    		{
    				FD_SET(tcp_sockServer, &fdSockSet);
    				if(socketMax < tcp_sockServer) {socketMax = tcp_sockServer};
    		}
    		for(int i = 0; i<num;i++)
    		{
    				if(tcp_accept[i].socket>0)
    				{
    						FD_SET(tcp_accept[i].socket, &fdSockSet);
    						if(socketMax <tcp_accept[i].socket) {socketMax = tcp_accept[i].socket};
    				}	
    		}
    if(socketMax > 0){
    		timeout.tv_sec = 5;
    		timeout.tv_usec = 0;
    		fd_act = 0;
    		fd_act = select(socketMax+1, &fdSockSet, NULL, NULL, &timeout);
    		if(fd_act>0)
    		{
    				for(int i = 0; i<num;i++)
    				{
    						if(tcp_accept[i].socket>0)
    						{
    								FD_SET(tcp_accept[i].socket, &fdSockSet);
    								if(FD_ISSET(tcp_accept[i].socket, &fdSockSet)) 
    								{
    										recv(tcp_accept[i].socket, rxbuf, SOCKET_TCP_RECV, 0);
    								}
    						}	
    				}	
    
    				if(FD_ISSET(tcp_sockServer, &fdSockSet)) 
    				{
    						newSocket = -1;
    						newSocket = accept(tcp_sockServer, (struct sockaddr *)&sockClient, (socklen_t *)&addrlen);	//sockclient为连接的客户端信息,addrlen为sockaddr结构体长度。					if(newSocket>0){
    						ip = ntohl(pTCP->sockClient.sin_addr.s_addr);
    						port = ntohs(pTCP->sockClient.sin_port); 		
    						sLinger.l_onoff = 0;
    						ret = setsockopt(newSocket, SOL_SOCKET, SO_LINGER, &sLinger, sizeof(struct linger));
    						lifeNumMax++;
    								if(lifeNumMax >= 100000000){
    									lifeNumMax = 1;
    									for(i=0;i<SOCKET_ACCEPT_MAX;i++){
    										if(tcpAccept[i].socket > 0){
    											tcpAccept[i].lifeNum -= 100000000;
    										}
    									}		
    								}
    								/* IP相同,则不需要关闭任何客户端 */
    								for (i = 0; i < SOCKET_ACCEPT_MAX; i++) {
    									if (tcpAccept[i].socket > 0 && tcpAccept[i].ip == ip) {
    										j = i;
    										goto accept_new_socket;
    									}
    								}		
    								/* 新来设备,判断是否num不够,如果num够则新增acceptsocket */
    								for (i = 0; i < SOCKET_ACCEPT_MAX; i++) {
    									if (tcpAccept[i].socket==0) {
    										j = i;
    										goto accept_new_socket;
    									}
    								}		
    								/* 新来设备,判断是否num不够,如果num不够则新增acceptsocket并放弃最早的客户端  */
    								j = 0;	
    								lifeNumMin = tcpAccept[0].lifeNum;
    								for (i = 1; i < SOCKET_ACCEPT_MAX; i++) {
    									if(lifeNumMin > tcpAccept[i].lifeNum){
    										lifeNumMin = tcpAccept[i].lifeNum;
    										j = i;
    									}
    								}	
    accept_new_socket:
    						
    								if(tcpAccept[j].socket > 0){
    									close(tcpAccept[j].socket);
    								}
    								tcpAccept[j].socket = newSocket;
    								tcpAccept[j].ip = ip;
    								tcpAccept[j].port = port;
    								tcpAccept[j].status = 0xff;	/* socket正常使用 */
    								tcpAccept[j].lifeNum = lifeNumMax;
    
    								  
    				}
    		}
    	
    
    }
    

    其中放弃最早的客户端采用了比较简单的算法
    每个客户端在连接时给予一个lifeNum生命值,还有一个不断增加的计数器 lifeNumMax,当客户端增加到最大数量时,比较前几个客户端的生命值,把最小的除去,因为它连接的最久。生命值代码为

    		lifeNumMax++;
    		if(lifeNumMax >= 100000000){
    			lifeNumMax = 1;
    			for(i=0;i<SOCKET_ACCEPT_MAX;i++){
    				if(tcpAccept[i].socket > 0){
    					tcpAccept[i].lifeNum -= 100000000;
    				}
    			}		
    		}
    
    展开全文
  • linux网络编程-很全的

    万次阅读 多人点赞 2018-08-15 21:25:54
    1. LINUX网络编程基础知识 1 1.1. TCP/IP协议概述 1 1.2. OSI参考模型及TCP/IP参考模型 1 1.3. TCP协议 3 1.4. UDP协议 5 1.5. 协议的选择 6 2. 网络相关概念 6 2.1. socket概念 7 2.2. socket...

    注:作者王晓,本人认为总结得很好,故记之,绝无侵权之意。

    1. LINUX网络编程基础知识 1

    1.1. TCP/IP协议概述 1

    1.2. OSI参考模型及TCP/IP参考模型 1

    1.3. TCP协议 3

    1.4. UDP协议 5

    1.5. 协议的选择 6

    2. 网络相关概念 6

    2.1. socket概念 7

    2.2. socket类型 8

    2.3. socket信息数据结构 8

    2.4. 数据存储优先顺序的转换 8

    2.5. 地址格式转化 9

    2.6. 名字地址转化 10

    3. socket编程 13

    3.1. 使用TCP协议的流程图 13

    3.2. 使用UDP协议的流程图 24

    3.3. 设置套接口的选项setsockopt的用法 31

    3.4. 单播、广播、组播(多播) 32

     

    1. LINUX网络编程基础知识

    1.1. TCP/IP协议概述

    协议protocol:通信双方必须遵循的规矩 由iso规定  rpc文档

    osi参考模型:(应-表-会-传-网-数-物)

    è 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层

    tcp/ip模型4层:

    应用层{http超文本传输协议   ftp文件传输协议  telnet远程登录   ssh安全外壳协议  stmp简单邮件发送   pop3收邮件}

    传输层{tcp传输控制协议,udp用户数据包协议}

    网络层{ip网际互联协议 icmp网络控制消息协议 igmp网络组管理协议}

    网络接口层{arp地址转换协议,rarp反向地址转换协议,mpls多协议标签交换}

    TCP协议:传输控制协议 面向连接的协议 能保证传输安全可靠 速度慢(有3次握手)

    UDP协议:用户数据包协议 非面向连接  速度快 不可靠

    通常是ip地址后面跟上端口号:ip用来定位主机  port区别应用(进程)

    http的端口号80  ssh-->22  telnet-->23  ftp-->21  用户自己定义的通常要大于1024

    1.2. OSI参考模型及TCP/IP参考模型

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    TCP/IP协议族的每一层的作用:

    ·网络接口层:负责将二进制流转换为数据帧,并进行数据帧的发送和接收。要注意的是数据帧是独立的网络信息传输单元。

    ·网络层:负责将数据帧封装成IP数据报,并运行必要的路由算法。

    ·传输层:负责端对端之间的通信会话连接和建立。传输协议的选择根据数据传输方式而定。

    ·应用层:负责应用程序的网络访问,这里通过端口号来识别各个不同的进程。

     

    TCP/IP协议族的每一层协议的相关注解:

    ·ARP:(地址转换协议)用于获得同一物理网络中的硬件主机地址。

    ·MPLS:(多协议标签交换)很有发展前景的下一代网络协议。

    ·IP:(网际互联协议)负责在主机和网络之间寻址和路由数据包。

    ·ICMP:(网络控制消息协议)用于发送报告有关数据包的传送错误的协议。

    ·IGMP:(网络组管理协议)被IP主机用来向本地多路广播路由器报告主机组成员的协议。

    ·TCP:(传输控制协议)为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到相应的应用程序。

    ·UDP:(用户数据包协议)提供了无连接通信,且不对传送包进行可靠的保证。适合于一次传输少量数据。

     

     

    1.3. TCP协议

    (1) 概述

    TCP是TCP/IP体系中面向连接的运输层协议,它提供全双工和可靠交付的服务。它采用许多机制来确保端到端结点之间的可靠数据传输,如采用序列号、确认重传、滑动窗口等。

    首先,TCP要为所发送的每一个报文段加上序列号,保证每一个报文段能被接收方接收,并只被正确的接收一次。

    其次,TCP采用具有重传功能的积极确认技术作为可靠数据流传输服务的基础。这里“确认”是指接收端在正确收到报文段之后向发送端回送一个确认(ACK)信息。发送方将每个已发送的报文段备份在自己的缓冲区里,而且在收到相应的确认之前是不会丢弃所保存的报文段的。“积极”是指发送发在每一个报文段发送完毕的同时启动一个定时器,加入定时器的定时期满而关于报文段的确认信息还没有达到,则发送发认为该报文段已经丢失并主动重发。为了避免由于网络延时引起迟到的确认和重复的确认,TCP规定在确认信息中捎带一个报文段的序号,使接收方能正确的将报文段与确认联系起来。

    最后,采用可变长的滑动窗口协议进行流量控制,以防止由于发送端与接收端之间的不匹配而引起的数据丢失。这里所采用的滑动窗口协议与数据链路层的滑动窗口协议在工作原理上完全相同,唯一的区别在于滑动窗口协议用于传输层是为了在端对端节点之间实现流量控制,而用于数据链路层是为了在相邻节点之间实现流量控制。TCP采用可变长的滑动窗口,使得发送端与接收端可根据自己的CPU和数据缓存资源对数据发送和接收能力来进行动态调整,从而灵活性更强,也更合理。

    (2) 三次握手协议

    在利用TCP实现源主机和目的主机通信时,目的主机必须同意,否则TCP连接无法建立。为了确保TCP连接的成功建立,TCP采用了一种称为三次握手的方式,三次握手方式使得“序号/确认号”系统能够正常工作,从而使它们的序号达成同步。如果三次握手成功,则连接建立成功,可以开始传送数据信息。

    其三次握手分别为:

    1)源主机A的TCP向主机B发送连接请求报文段,其首部中的SYN(同步)标志位应置为1,表示想跟目标主机B建立连接,进行通信,并发送一个同步序列号X(例:SEQ=100)进行同步,表明在后面传送数据时的第一个数据字节的序号为X+1(即101)。

    2)目标主机B的TCP收到连接请求报文段后,如同意,则发回确认。再确认报中应将ACK位和SYN位置为1.确认号为X+1,同时也为自己选择一个序号Y。

    3)源主机A的TCP收到目标主机B的确认后要想目标主机B给出确认。其ACK置为1,确认号为Y+1,而自己的序号为X+1。TCP的标准规定,SYN置1的报文段要消耗掉一个序号。

    运行客户进程的源主机A的TCP通知上层应用进程,连接已经建立。当源主机A向目标主机B发送第一个数据报文段时,其序号仍为X+1,因为前一个确认报文段并不消耗序号。

    当运行服务进程的目标主机B的TCP收到源主机A的确认后,也通知其上层应用进程,连接已经建立。至此建立了一个全双工的连接。

     

    三次握手:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程序。 

    (3) TCP数据报头

    TCP头信息

     

    ·源端口、目的端口:16位长。标识出远端和本地的端口号。

    ·序号:32位长。标识发送的数据报的顺序。

    ·确认号:32位长。希望收到的下一个数据报的序列号。

    ·TCP头长:4位长。表明TCP头中包含多少个32位字。

    ·6位未用。

    ·ACK:ACK位置1表明确认号是合法的。如果ACK为0,那么数据报不包含确认信息,确认字段被省略。

    ·PSH:表示是带有PUSH标志的数据。接收方因此请求数据报一到便可送往应用程序而不必等到缓冲区装满时才发送。

    ·RST:用于复位由于主机崩溃或其他原因而出现的错误的连接。还可以用于拒绝非法的数据报或拒绝连接请求。

    ·SYN:用于建立连接。

    ·FIN:用于释放连接。

    ·窗口大小:16位长。窗口大小字段表示在确认了字节之后还可以发送多少个字节。

    ·校验和:16位长。是为了确保高可靠性而设置的。它校验头部、数据和伪TCP头部之和。

    ·可选项:0个或多个32位字。包括最大TCP载荷,窗口比例、选择重复数据报等选项。

    1.4. UDP协议

    (1) 概述

    UDP即用户数据报协议,它是一种无连接协议,因此不需要像TCP那样通过三次握手来建立一个连接。同时,一个UDP应用可同时作为应用的客户或服务器方。由于UDP协议并不需要建立一个明确的连接,因此建立UDP应用要比建立TCP应用简单得多。

    它比TCP协议更为高效,也能更好地解决实时性的问题。如今,包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都使用UDP协议。 

    (2) Udp数据包头格式

     

    1.5. 协议的选择

    (1)对数据可靠性的要求

    对数据要求高可靠性的应用需选择TCP协议,如验证、密码字段的传送都是不允许出错的,而对数据的可靠性要求不那么高的应用可选择UDP传送。

    (2)应用的实时性

    TCP协议在传送过程中要使用三次握手、重传确认等手段来保证数据传输的可靠性。使用TCP协议会有较大的时延,因此不适合对实时性要求较高的应用,如VOIP、视频监控等。相反,UDP协议则在这些应用中能发挥很好的作用。

    (3)网络的可靠性

    由于TCP协议的提出主要是解决网络的可靠性问题,它通过各种机制来减少错误发生的概率。因此,在网络状况不是很好的情况下需选用TCP协议(如在广域网等情况),但是若在网络状况很好的情况下(如局域网等)就不需要再采用TCP协议,而建议选择UDP协议来减少网络负荷。 

    2. 网络相关概念

    1)套接口的概念:

    套接口,也叫“套接字”。是操作系统内核中的一个数据结构,它是网络中的节点进行相互通信的门户。它是网络进程的ID。网络通信,归根到底还是进程间的通信(不同计算机上的进程间通信)。在网络中,每一个节点(计算机或路由)都有一个网络地址,也就是IP地址。两个进程通信时,首先要确定各自所在的网络节点的网络地址。但是,网络地址只能确定进程所在的计算机,而一台计算机上很可能同时运行着多个进程,所以仅凭网络地址还不能确定到底是和网络中的哪一个进程进行通信,因此套接口中还需要包括其他的信息,也就是端口号(PORT)。在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。所以,使用端口号和网络地址的组合可以唯一的确定整个网络中的一个网络进程。

    例如,如网络中某一台计算机的IP为10.92.20.160,操作系统分配给计算机中某一应用程序进程的端口号为1500,则此时 10.92.20.160  1500就构成了一个套接口。

    2)端口号的概念:

    在网络技术中,端口大致有两种意思:一是物理意义上的端口,如集线器、交换机、路由器等用于连接其他网络设备的接口。二是指TCP/IP协议中的端口,端口号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的“周知的端口”,其值一般为0~1023.例如http的端口号是80,ftp为21,ssh为22,telnet为23等。还有一类是用户自己定义的,通常是大于1024的整型值。

    3)ip地址的表示:

    通常用户在表达IP地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在通常使用的socket编程中使用的则是二进制值,这就需要将这两个数值进行转换。

    ipv4地址:32bit, 4字节,通常采用点分十进制记法。

    例如对于:10000000 00001011 00000011 00011111

    点分十进制表示为:128.11.3.31

    ip地址的分类:

     

    特殊的ip地址:

     

    2.1. socket概念

    Linux中的网络编程是通过socket接口来进行的。socket是一种特殊的I/O接口,它也是一种文件描述符。它是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。

    每一个socket都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。socket也有一个类似于打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立、数据传输等操作都是通过socket来实现的;

    2.2. socket类型 

    (1)流式socket(SOCK_STREAM) à用于TCP通信

    流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。

    (2)数据报socket(SOCK_DGRAM) à用于UDP通信

    数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。

    (3)原始socket (SOCK_RAW) à用于新的网络协议实现的测试等

    原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。 

    2.3. socket信息数据结构

    struct sockaddr

    {

         unsigned short sa_family; /*地址族*/

         char sa_data[14]; /*14字节的协议地址,包含该socket的IP地址和端口号。*/

    };

    struct sockaddr_in

    {

         short int sa_family; /*地址族*/

         unsigned short int sin_port; /*端口号*/

         struct in_addr sin_addr; /*IP地址*/

         unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr同样大小*/

    };

    struct in_addr

    {

    unsigned long int  s_addr; /* 32位IPv4地址,网络字节序 */

    };

    头文件<netinet/in.h>

    sa_family:AF_INET  àIPv4协议   AF_INET6  àIPv6协议

    2.4. 数据存储优先顺序的转换

    计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式)。内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式。内存的高地址存储数据的低字节,低地址存储数据高字节的方式称为大端模式。

    eg:对于内存中存放的数0x12345678来说

    如果是采用大端模式存放的,则其真实的数是:0x12345678

    如果是采用小端模式存放的,则其真实的数是:0x78563412

    如果称某个系统所采用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。而端口号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。这里用到四个函数:htons(),ntohs(),htonl()和ntohl().这四个地址分别实现网络字节序和主机字节序的转化,这里的h代表host,n代表network,s代表short,l代表long。通常16位的IP端口号用s代表,而IP地址用l来代表。

    函数原型如下:

     

    2.5. 地址格式转化

    通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址),而在通常使用的socket编程中使用的则是32位的网络字节序的二进制值,这就需要将这两个数值进行转换。这里在Ipv4中用到的函数有inet_aton()、inet_addr()和inet_ntoa(),而IPV4和Ipv6兼容的函数有inet_pton()和inet_ntop()。

    IPv4的函数原型:

    #include <sys/socket.h>

      #include <netinet/in.h>

      #include <arpa/inet.h>

    int inet_aton(const char *straddr, struct in_addr *addrptr);

    char *inet_ntoa(struct in_addr inaddr);

    in_addr_t inet_addr(const char *straddr);

    函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值。返回值:成功,则返回1,不成功返回0.

    参数straddr:存放输入的点分十进制数IP地址字符串。

    参数addrptr:传出参数,保存网络字节序的32位二进制数值。

    函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址。

    函数inet_addr():功能与inet_aton相同,但是结果传递的方式不同。inet_addr()若成功则返回32位二进制的网络字节序地址。

    IPv4和IPv6的函数原型:

    #include <arpa/inet.h>

    int inet_pton(int family, const char *src, void *dst);

    const char *inet_ntop(int family, const void *src, char *dst, socklen_t len);

    函数inet_pton跟inet_aton实现的功能类似,只是多了family参数,该参数指定为AF_INET,表示是IPv4协议,如果是AF_INET6,表示IPv6协议。

    函数inet_ntop跟inet_ntoa类似,其中len表示表示转换之后的长度(字符串的长度)。

    Example:

    #include <stdio.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    int main()

    {

    char ip[] = "192.168.0.101";

     

    struct in_addr myaddr;

    /* inet_aton */

    int iRet = inet_aton(ip, &myaddr);

    printf("%x\n", myaddr.s_addr);

     

    /* inet_addr */

    printf("%x\n", inet_addr(ip));

     

    /* inet_pton */

    iRet = inet_pton(AF_INET, ip, &myaddr);

    printf("%x\n", myaddr.s_addr);

     

    myaddr.s_addr = 0xac100ac4;

    /* inet_ntoa */

    printf("%s\n", inet_ntoa(myaddr));

     

    /* inet_ntop */

    inet_ntop(AF_INET, &myaddr, ip, 16);

    puts(ip);

    return 0;

    }

    2.6. 名字地址转化

    通常,人们在使用过程中都不愿意记忆冗长的IP地址,尤其到Ipv6时,地址长度多达128位,那时就更加不可能一次性记忆那么长的IP地址了。因此,使用主机名或域名将会是很好的选择。主机名与域名的区别:主机名通常在局域网里面使用,通过/etc/hosts文件,主机名可以解析到对应的ip;域名通常是再internet上使用。

    众所周知,百度的域名为:www.baidu.com,而这个域名其实对应了一个百度公司的IP地址,那么百度公司的IP地址是多少呢?我们可以利用ping www.baidu.com来得到百度公司的ip地址,如图。那么,系统是如何将www.baidu.com 这个域名转化为IP地址220.181.111.148的呢?

     

    在linux中,有一些函数可以实现主机名和地址的转化,最常见的有gethostbyname()、gethostbyaddr()等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化。其中gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作,是将IP地址转化为主机名。

    函数原型:

    #include <netdb.h>

    struct hostent* gethostbyname(const char* hostname);

    struct hostent* gethostbyaddr(const char* addr, size_t len, int family);

    结构体:

    struct hostent

    {

         char *h_name; /*正式主机名*/

         char **h_aliases; /*主机别名*/

         int h_addrtype; /*主机IP地址类型 IPv4为AF_INET*/

         int h_length; /*主机IP地址字节长度,对于IPv4是4字节,即32位*/

         char **h_addr_list; /*主机的IP地址列表*/

    }

    #define  h_addr  h_addr_list[0] /*保存的是ip地址*/

    函数gethostbyname():用于将域名(www.baidu.com)或主机名转换为IP地址。参数hostname指向存放域名或主机名的字符串。

    函数gethostbyaddr():用于将IP地址转换为域名或主机名。参数addr是一个IP地址,此时这个ip地址不是普通的字符串,而是要通过函数inet_aton()转换。len为IP地址的长度,AF_INET为4。family可用AF_INET:Ipv4或AF_INET6:Ipv6。

     

    Example1:将百度的www.baidu.com 转换为ip地址

    #include <netdb.h>

    #include <sys/socket.h>

    #include <stdio.h>

    int main(int argc, char **argv)

    {

        char *ptr, **pptr;

        struct hostent *hptr;

        char str[32] = {'\0'};

    /* 取得命令后第一个参数,即要解析的域名或主机名 */

        ptr = argv[1];  //如www.baidu.com

    /* 调用gethostbyname()。结果存在hptr结构中 */

        if((hptr = gethostbyname(ptr)) == NULL)

        {

            printf(" gethostbyname error for host:%s\n", ptr);

            return 0;

        }

    /* 将主机的规范名打出来 */

        printf("official hostname:%s\n", hptr->h_name);

    /* 主机可能有多个别名,将所有别名分别打出来 */

        for(pptr = hptr->h_aliases; *pptr != NULL; pptr++)

            printf(" alias:%s\n", *pptr);

    /* 根据地址类型,将地址打出来 */

        switch(hptr->h_addrtype)

        {

    case AF_INET:

    case AF_INET6:

    pptr = hptr->h_addr_list;

    /* 将刚才得到的所有地址都打出来。其中调用了inet_ntop()函数 */

    for(; *pptr!=NULL; pptr++)

    printf(" address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));

    printf(" first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));

            break;

    default:

    printf("unknown address type\n");

            break;

        }

        return 0;

    }

    编译运行

    #gcc test.c

    #./a.out www.baidu.com

    official hostname:www.a.shifen.com

    alias:www.baidu.com

    address: 220.181.111.148

    ……

    first address: 220.181.111.148

    (注:这里需要联网才能访问www.baidu.com。可以尝试用自己的虚拟机的主机名,利用命令hostname可以查看自己的主机名,用hostname –i可以查看主机名对应的ip地址。那么如何修改主机名呢?直接用hostname wangxiao只是暂时有效,重启之后就没有了,想要永久有效,需要修改/etc/sysconfig/network将HOSTNAME修改,当然要重启虚拟机。如果ip地址不对,你可以修改/etc/hosts这个文件,添加你自己的主机名对应的ip地址)

    3. socket编程

    3.1. 使用TCP协议的流程图

    TCP通信的基本步骤如下:

    服务端:socket---bind---listen---while(1){---accept---recv---send---close---}---close

    客户端:socket----------------------------------connect---send---recv-----------------close

     

    服务器端:

    1. 头文件包含:

        #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <unistd.h>

    #include <string.h>

    #include <stdio.h>

    #include <stdlib.h>

     

    2. socket函数:生成一个套接口描述符。

        原型:int socket(int domain,int type,int protocol);

    参数:domainà{ AF_INET:Ipv4网络协议   AF_INET6:IPv6网络协议}

      typeà{tcp:SOCK_STREAM   udp:SOCK_DGRAM}

      protocolà指定socket所使用的传输协议编号。通常为0.

    返回值:成功则返回套接口描述符,失败返回-1。

    常用实例:int sfd = socket(AF_INET, SOCK_STREAM, 0);

          if(sfd == -1){perror("socket");exit(-1);}

     

    3. bind函数:用来绑定一个端口号和IP地址,使套接口与指定的端口号和IP地址相关联。

    原型:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);

    参数:sockfdà为前面socket的返回值。

      my_addrà为结构体指针变量

    对于不同的socket domain定义了一个通用的数据结构 struct sockaddr  //此结构体不常用 {     unsigned short int sa_family;  //调用socket()时的domain参数,即AF_INET值。     char sa_data[14];  //最多使用14个字符长度 }; 此sockaddr结构会因使用不同的socket domain而有不同结构定义, 例如使用AF_INET domain,其socketaddr结构定义便为 struct sockaddr_in  //常用的结构体 {     unsigned short int sin_family;  //即为sa_family èAF_INET     uint16_t sin_port;  //为使用的port编号     struct in_addr sin_addr;  //为IP 地址     unsigned char sin_zero[8];  //未使用 }; struct in_addr {     uint32_t s_addr; }; addrlenàsockaddr的结构体长度。通常是计算sizeof(struct sockaddr);

    返回值:成功则返回0,失败返回-1

    常用实例:struct sockaddr_in my_addr;  //定义结构体变量

      memset(&my_addr, 0, sizeof(struct sockaddr)); //将结构体清空

      //或bzero(&my_addr, sizeof(struct sockaddr));

      my_addr.sin_family = AF_INET;  //表示采用Ipv4网络协议

      my_addr.sin_port = htons(8888);  //表示端口号为8888,通常是大于1024的一个值。

    //htons()用来将参数指定的16位hostshort转换成网络字符顺序

    my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); // inet_addr()用来将IP地址字符串转换成网络所使用的二进制数字,如果为INADDR_ANY,这表示服务器自动填充本机IP地址。

    if(bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr)) == -1)

    {perror("bind");close(sfd);exit(-1);}

    (注:通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。)

     

    4. listen函数:使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。

    原型:int listen(int sockfd, int backlog);

    参数:sockfdà为前面socket的返回值.即sfd

    backlogà指定同时能处理的最大连接要求,通常为10或者5。 最大值可设至128

    返回值:成功则返回0,失败返回-1

    常用实例:if(listen(sfd, 10) == -1)

      {perror("listen");close(sfd);exit(-1);}

     

    5. accept函数:接受远程计算机的连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求。(也就是说,类似于移动营业厅,如果有客户打电话给10086,此时服务器就会请求连接,处理一些事务之后,就通知一个话务员接听客户的电话,也就是说,后面的所有操作,此时已经于服务器没有关系,而是话务员跟客户的交流。对应过来,客户请求连接我们的服务器,我们服务器先做了一些绑定和监听等等操作之后,如果允许连接,则调用accept函数产生一个新的套接字,然后用这个新的套接字跟我们的客户进行收发数据。也就是说,服务器跟一个客户端连接成功,会有两个套接字。)

    原型:int accept(int s,struct sockaddr * addr,int * addrlen);

    参数:sà为前面socket的返回值.即sfd

      addrà为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。

      addrlenà表示结构体的长度,为整型指针

    返回值:成功则返回新的socket处理代码new_fd,失败返回-1

    常用实例:struct sockaddr_in clientaddr;

      memset(&clientaddr, 0, sizeof(struct sockaddr));

      int addrlen = sizeof(struct sockaddr);

      int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);

      if(new_fd == -1)

      {perror("accept");close(sfd);exit(-1);}

      printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

     

    6. recv函数:用新的套接字来接收远端主机传来的数据,并把数据存到由参数buf 指向的内存空间

    原型:int recv(int sockfd,void *buf,int len,unsigned int flags);

    参数:sockfdà为前面accept的返回值.即new_fd,也就是新的套接字。

      bufà表示缓冲区

      lenà表示缓冲区的长度

      flagsà通常为0

    返回值:成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1

    常用实例:char buf[512] = {0};

      if(recv(new_fd, buf, sizeof(buf), 0) == -1)

      {perror("recv");close(new_fd);close(sfd);exit(-1);}

      puts(buf);

     

    7. send函数:用新的套接字发送数据给指定的远端主机

    原型:int send(int s,const void * msg,int len,unsigned int flags);

    参数:sà为前面accept的返回值.即new_fd

      msgà一般为常量字符串

      lenà表示长度

      flagsà通常为0

    返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1

    常用实例:if(send(new_fd, "hello", 6, 0) == -1)

    {perror("send");close(new_fd);close(sfd);exit(-1);}

     

    8. close函数:当使用完文件后若已不再需要则可使用close()关闭该文件,并且close()会让数据写回磁盘,并释放该文件所占用的资源

    原型:int close(int fd);

    参数:fdà为前面的sfd,new_fd

      返回值:若文件顺利关闭则返回0,发生错误时返回-1

    常用实例:close(new_fd);

      close(sfd);

    客户端:

    1. connect函数:用来请求连接远程服务器,将参数sockfd 的socket 连至参数serv_addr 指定的服务器IP和端口号上去。

    原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);

    参数:sockfdà为前面socket的返回值,即sfd

      serv_addrà为结构体指针变量,存储着远程服务器的IP与端口号信息。

      addrlenà表示结构体变量的长度

    返回值:成功则返回0,失败返回-1

    常用实例:struct sockaddr_in seraddr;//请求连接服务器

      memset(&seraddr, 0, sizeof(struct sockaddr));

      seraddr.sin_family = AF_INET;

      seraddr.sin_port = htons(8888); //服务器的端口号

      seraddr.sin_addr.s_addr = inet_addr("192.168.0.101");  //服务器的ip

      if(connect(sfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr)) == -1)

      {perror("connect");close(sfd);exit(-1);}

     

    将上面的头文件以及各个函数中的代码全部拷贝就可以形成一个完整的例子,此处省略。

    还可以不写客户端程序,使用telnet远程登录来检测我们的服务器端程序。比如我们的服务器程序在监听8888端口,我们可以用telnet 192.168.0.101 8888来查看服务端的状况。

    Example:将一些通用的代码全部封装起来,以后要用直接调用函数即可。如下:

    通用网络封装代码头文件: tcp_net_socket.h

    #ifndef __TCP__NET__SOCKET__H

    #define __TCP__NET__SOCKET__H

     

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <unistd.h>

    #include <signal.h>

     

    extern int tcp_init(const char* ip,int port);

    extern int tcp_accept(int sfd);

    extern int tcp_connect(const char* ip,int port);

    extern void signalhandler(void);

     

    #endif

    具体的通用函数封装如下: tcp_net_socket.c

    #include "tcp_net_socket.h"

    int tcp_init(const char* ip, int port)   //用于初始化操作

    {

        int sfd = socket(AF_INET, SOCK_STREAM, 0);//首先创建一个socket,向系统申请

        if(sfd == -1)

        {

            perror("socket");

            exit(-1);

        }

        struct sockaddr_in serveraddr;

        memset(&serveraddr, 0, sizeof(struct sockaddr));

        serveraddr.sin_family = AF_INET;

        serveraddr.sin_port = htons(port);

        serveraddr.sin_addr.s_addr = inet_addr(ip);//或INADDR_ANY

    if(bind(sfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)) == -1)

    //将新的socket与制定的ip、port绑定

        {

            perror("bind");

            close(sfd);

            exit(-1);

        }

        if(listen(sfd, 10) == -1)//监听它,并设置其允许最大的连接数为10个

        {

            perror("listen");

            close(sfd);

            exit(-1);

        }

        return sfd;

    }

     

    int tcp_accept(int sfd)   //用于服务端的接收

    {

        struct sockaddr_in clientaddr;

        memset(&clientaddr, 0, sizeof(struct sockaddr));

        int addrlen = sizeof(struct sockaddr);

    int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);

    //sfd接受客户端连接,并创建新的socket为new_fd,将请求连接的客户端的ip、port保存在结构体clientaddr中

        if(new_fd == -1)

        {

            perror("accept");

            close(sfd);

            exit(-1);

        }

        printf("%s %d success connect...\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

    return new_fd;

    }

     

    int tcp_connect(const char* ip, int port)   //用于客户端的连接

    {

        int sfd = socket(AF_INET, SOCK_STREAM, 0);//向系统注册申请新的socket

        if(sfd == -1)

        {

            perror("socket");

            exit(-1);

        }

        struct sockaddr_in serveraddr;

        memset(&serveraddr, 0, sizeof(struct sockaddr));

        serveraddr.sin_family = AF_INET;

        serveraddr.sin_port = htons(port);

        serveraddr.sin_addr.s_addr = inet_addr(ip);

    if(connect(sfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)) == -1)

    //将sfd连接至制定的服务器网络地址serveraddr

        {

    perror("connect");

    close(sfd);

    exit(-1);

        }

        return sfd;

    }

     

    void signalhandler(void)   //用于信号处理,让服务端在按下Ctrl+c或Ctrl+\的时候不会退出

    {

    sigset_t sigSet;

        sigemptyset(&sigSet);

        sigaddset(&sigSet,SIGINT);

        sigaddset(&sigSet,SIGQUIT);

        sigprocmask(SIG_BLOCK,&sigSet,NULL);

    }

    服务器端: tcp_net_server.c

    #include "tcp_net_socket.h"

    int main(int argc, char* argv[])

    {

    if(argc < 3)

    {

    printf("usage:./servertcp  ip  port\n");

    exit(-1);

    }

    signalhandler();

    int sfd = tcp_init(argv[1], atoi(argv[2]));  //或int sfd = tcp_init("192.168.0.164", 8888);

    while(1) //用while循环表示可以与多个客户端接收和发送,但仍是阻塞模式的

    {

    int cfd = tcp_accept(sfd);

    char buf[512] = {0};

    if(recv(cfd, buf, sizeof(buf), 0) == -1)//从cfd客户端接收数据存于buf中

    {

    perror("recv");

    close(cfd);

    close(sfd);

    exit(-1);

    }

    puts(buf);

    if(send(cfd, "hello world", 12, 0) == -1)//从buf中取向cfd客户端发送数据

    {

    perror("send");

    close(cfd);

    close(sfd);

    exit(-1);

    }

    close(cfd);

    }

        close(sfd);

    }

    客户端: tcp_net_client.c

    #include "tcp_net_socket.h"

    int main(int argc, char* argv[])

    {

        if(argc < 3)

        {

    printf("usage:./clienttcp  ip  port\n");

    exit(-1);

        }

        int sfd = tcp_connect(argv[1],atoi(argv[2]));

        char buf[512] = {0};

        send(sfd, "hello", 6, 0);     //向sfd服务端发送数据

        recv(sfd, buf, sizeof(buf), 0); //从sfd服务端接收数据

        puts(buf);

        close(sfd);

    }

    #gcc –o tcp_net_server tcp_net_server.c tcp_net_socket.c

    #gcc –o tcp_net_client tcp_net_client.c tcp_net_socket.c

    #./tcp_net_server 192.168.0.164 8888

    #./tcp_net_client 192.168.0.164 8888

    /* 备注

    可以通过将上述经常用到的函数做成动态库,这样以后再用到的时候就可以直接用。步骤如下:

    gcc –fpic –c tcp_net_socket.c –o tcp_net_socket.o

    gcc –shared tcp_net_socket.o –o libtcp_net_socket.so

    cp lib*.so /lib    //这样以后就可以直接使用该库了

    cp tcp_net_socket.h /usr/include/    //这样头文件包含可以用include <tcp_net_socket.h>了

    gcc –o main main.c –ltcp_net_socket  //其中main.c要包含头文件 :  include <tcp_net_socket.h>

    ./main

    */

    注:上面的虽然可以实现多个客户端访问,但是仍然是阻塞模式(即一个客户访问的时候会阻塞不让另外的客户访问)。解决办法有:

    1. 多进程(因为开销比较大,所以不常用)

    #include <tcp_net_socket.h>

     

    int main()

    {

    int sfd = tcp_init("192.168.0.101", 8888);

    while(1)

    {

    int cfd = tcp_accept(sfd);

    if(fork() == 0)

    {

    send(cfd, "hello", 6, 0);

    sleep(10);

    close(cfd);

    }

    else

    {

    close(cfd);

    }

    }

    close(sfd);

    return 0;

    }

    1. 多线程

    #include <tcp_net_socket.h>

    #include <pthread.h>

     

    void* pthfunc(void* arg)

    {

    int cfd = (int)arg;

    send(cfd, "hello", 6, 0);

    sleep(10);

    close(cfd);

    }

     

    int main()

    {

    int sfd = tcp_init("192.168.0.101", 8888);

    pthread_t pthid = 0;

    while(1)

    {

        int cfd = tcp_accept(sfd);

        pthread_create(&pthid, NULL, pthfunc, (void*)cfd);

    }

        close(sfd);

    return 0;

    }

    /* 备注 读写大容量的文件时,通过下面的方法效率很高

    ssize_t readn(int fd, char *buf, int size)//读大量内容

    {

    char *pbuf = buf;

    int total ,nread;

    for(total = 0; total < size; )

    {

    nread=read(fd,pbuf,size-total);

    if(nread==0)

           return total;

        if(nread == -1)

        {

          if(errno == EINTR)

              continue;

          else

             return -1;

        }

        total += nread;

        pbuf += nread;

        }

       return total;

    }

    ssize_t writen(int fd, char *buf, int size)//写大量内容

    {

       char *pbuf=buf;

       int total ,nwrite;

       for(total = 0; total < size; )

       {

          nwrite=write(fd,pbuf,size-total);

      if( nwrite <= 0 )

      {

       if( nwrite == -1 && errno == EINTR )

    continue;

       else

        return -1;

      }

         total += nwrite;

         pbuf += nwrite;

       }

      return total;

    }

    */

    1. 调用fcntl将sockfd设置为非阻塞模式。(不常见)

    #include <unistd.h>

    #include <fcntl.h>

    ……

    sockfd = socket(AF_INET,SOCK_STREAM,0);

    iflags = fcntl(sockfd, F_GETFL, 0);

    fcntl(sockfd,F_SETFL,O_NONBLOCK | iflags);

    ……

    1. 多路选择select

    #include <sys/select.h>

    #include "tcp_net_socket.h"

    #define MAXCLIENT 10

    main()

    {

    int sfd = tcp_init("192.168.0.164", 8888);

    int fd = 0;

    char buf[512] = {0};

    fd_set rdset;

    while(1)

    {

    FD_ZERO(&rdset);

    FD_SET(sfd,&rdset);

    if(select(MAXCLIENT + 1, &rdset, NULL, NULL, NULL) < 0)

    continue;

    for(fd = 0; fd < MAXCLIENT; fd++)

    {

    if(FD_ISSET(fd,&rdset))

    {

    if(fd == sfd)

    {

    int cfd = tcp_accept(sfd);

    FD_SET(cfd,&rdset);

    //……

    }

    else

    {

    bzero(buf, sizeof(buf));

    recv(fd, buf, sizeof(buf), 0);

    puts(buf);

    send(fd, "java", 5, 0);

    //FD_CLR(fd, &rdset);

    close(fd);

    }

    }

    }

    }

    close(sfd);

    }

    具体例子请参考《网络编程之select.doc》或《tcp_select》

    3.2. 使用UDP协议的流程图

    UDP通信流程图如下:

    服务端:socket---bind---recvfrom---sendto---close

    客户端:socket----------sendto---recvfrom---close

     

    ·sendto()函数原型:

    int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);

    该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。

    ·recvfrom()函数原型:

    int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);

    from是一个struct sockaddr类型的变量,该变量保存连接机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或 当出现错误时返回-1,并置相应的errno。

    Example:UDP的基本操作

    服务器端:

    #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <unistd.h>

    #include <string.h>

    #include <stdio.h>

    #include <stdlib.h>

    main()

    {

        int sfd = socket(AF_INET, SOCK_DGRAM, 0);

        if(sfd == -1)

        {

            perror("socket");

            exit(-1);

        }

     

        struct sockaddr_in saddr;

        bzero(&saddr, sizeof(saddr));

        saddr.sin_family = AF_INET;

        saddr.sin_port = htons(8888);

        saddr.sin_addr.s_addr = INADDR_ANY;

        if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)

        {

            perror("bind");

            close(sfd);

            exit(-1);

        }

     

        char buf[512] = {0};

        while(1)

        {

            struct sockaddr_in fromaddr;

            bzero(&fromaddr, sizeof(fromaddr));

            int fromaddrlen = sizeof(struct sockaddr);

            if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)

            {

                perror("recvfrom");

                close(sfd);

                exit(-1);

            }

    printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);

     

            sendto(sfd, "world", 6, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));

    }

     

        close(sfd);

    }

    客户端:

    #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <unistd.h>

    #include <string.h>

    #include <stdio.h>

    #include <stdlib.h>

    int main(int argc, char* argv[])

    {

        int sfd = socket(AF_INET, SOCK_DGRAM, 0);

        if(sfd == -1)

        {

            perror("socket");

            exit(-1);

        }

     

        struct sockaddr_in toaddr;

        bzero(&toaddr, sizeof(toaddr));

        toaddr.sin_family = AF_INET;

        toaddr.sin_port = htons(atoi(argv[2])); //此处的端口号要跟服务器一样

        toaddr.sin_addr.s_addr = inet_addr(argv[1]); //此处为服务器的ip

        sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));

     

        char buf[512] = {0};

        struct sockaddr_in fromaddr;

        bzero(&fromaddr, sizeof(fromaddr));

        int fromaddrlen = sizeof(struct sockaddr);

        if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)

        {

            perror("recvfrom");

            close(sfd);

            exit(-1);

        }

    printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);

     

        close(sfd);

    }

    Example:UDP发送文件  先发文件大小  再发文件内容

    服务器端:

    #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <unistd.h>

    #include <fcntl.h>

    #include <sys/stat.h>

    #include <string.h>

    #include <stdio.h>

    #include <stdlib.h>

    main()

    {

        int sfd = socket(AF_INET, SOCK_DGRAM, 0);

        if(sfd == -1)

        {

            perror("socket");

            exit(-1);

        }

     

        struct sockaddr_in saddr;

        bzero(&saddr, sizeof(saddr));

        saddr.sin_family = AF_INET;

        saddr.sin_port = htons(8888);

        saddr.sin_addr.s_addr = INADDR_ANY;

        if(bind(sfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)) == -1)

        {

            perror("bind");

            close(sfd);

            exit(-1);

        }

     

        char buf[512] = {0};

        struct sockaddr_in fromaddr;

        bzero(&fromaddr, sizeof(fromaddr));

        int fromaddrlen = sizeof(struct sockaddr);

        if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen) == -1)

        {

            perror("recvfrom");

            close(sfd);

            exit(-1);

        }

        printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf);

        

        FILE* fp = fopen("1.txt","rb");

        struct stat st;  //用于获取文件内容的大小

        stat("1.txt", &st);

        int filelen = st.st_size;

        sendto(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));

        while(!feof(fp))   //表示没有到文件尾

        {

            int len = fread(buf,1,sizeof(buf),fp);

            sendto(sfd, buf, len, 0, (struct sockaddr*)&fromaddr, sizeof(struct sockaddr));

    }

     

        close(sfd);

    }

    客户端:

    #include <sys/types.h>

    #include <sys/socket.h>

    #include <netinet/in.h>

    #include <arpa/inet.h>

    #include <unistd.h>

    #include <string.h>

    #include <stdio.h>

    #include <stdlib.h>

    #define BUFSIZE 512

    int main(int argc, char* argv[])

    {

        int sfd = socket(AF_INET, SOCK_DGRAM, 0);

        if(sfd == -1)

        {

            perror("socket");

            exit(-1);

        }

     

        struct sockaddr_in toaddr;

        bzero(&toaddr, sizeof(toaddr));

        toaddr.sin_family = AF_INET;

        toaddr.sin_port = htons(atoi(argv[2]));

        toaddr.sin_addr.s_addr = inet_addr(argv[1]);

        sendto(sfd, "hello", 6, 0, (struct sockaddr*)&toaddr, sizeof(struct sockaddr));

     

        char buf[BUFSIZE] = {0};

        struct sockaddr_in fromaddr;

        bzero(&fromaddr, sizeof(fromaddr));

        int fromaddrlen = sizeof(struct sockaddr);

        int filelen = 0;   //用于保存文件长度

        FILE* fp = fopen("2.txt","w+b");

    //接收文件的长度

    recvfrom(sfd, (void*)&filelen, sizeof(int), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);

        printf("the length of file is %d\n",filelen);

        printf("Create a new file!\n");

        printf("begin to reveive file content!\n");

        //接收文件的内容

    while(1)

        {

            int len = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&fromaddr, &fromaddrlen);

            if(len < BUFSIZE)

    //如果接收的长度小于BUFSIZE,则表示最后一次接收,此时要用break退出循环

            {

                fwrite(buf,sizeof(char),len,fp);

                break;

            }

            fwrite(buf,sizeof(char),len,fp);

        }

        printf("receive file finished!\n");

        close(sfd);

    }

    3.3. 设置套接口的选项setsockopt的用法

    函数原型:

    #include <sys/types.h >

      #include <sys/socket.h>

      int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

    sockfd:标识一个套接口的描述字

    level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6

    optname:需设置的选项

    optval:指针,指向存放选项值的缓冲区

    optlen:optval缓冲区长度

     

    全部都必须要放在bind之前,另外通常是用于UDP的。

    1. 如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:

    int reuse=1;

    setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(int));

    2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:

    int reuse=0;

    setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(int));

    3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:

    int nNetTimeout=1000; // 1秒

    // 发送时限

    setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));

    // 接收时限

    setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));

    4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步),系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:

    // 接收缓冲区

    int nRecvBuf=32*1024;    // 设置为32K

    setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

    // 发送缓冲区

    int nSendBuf=32*1024;    // 设置为32K

    setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

    5. 如果在发送数据时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:

    int nZero=0;

    setsockopt(socket,SOL_SOCKET,SO_SNDBUF,(char *)&nZero,sizeof(int));

    6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):

    int nZero=0;

    setsockopt(socket,SOL_SOCKET,SO_RCVBUF,(char *)&nZero,sizeof(int));

    7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:

    int bBroadcast = 1;

    setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(int));

    3.4. 单播、广播、组播(多播)

    多播广播是用于建立分步式系统:例如网络游戏、ICQ聊天构建、远程视频会议系统的重要工具。使用多播广播的程序和UDP的单播程序相似。区别在于多播广播程序使用特殊的IP地址。

    对于单播而言,单播用于两个主机之间的端对端通信。

    对于广播而言,广播用于一个主机对整个局域网上所有主机上的数据通信。广播只能用于客户机向服务器广播,因为客户机要指明广播的IP地址“192.168.0.255”和广播的端口号。服务器端bing的时候,绑定的端口号要跟广播的端口号是同一个。这样才能收到广播消息。实例请参考《udp_广播》。

    对于多播而言,也称为“组播”,将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网上的主机进行通信。实际情况下,经常需要对一组特定的主机进行通信,而不是整个局域网上的所有主机,这就是多播的用途。例如,我们通常所说的讨论组。IPv4多播地址采用D类IP地址确定多播的组。在Internet中,多播地址范围是从224.0.0.0到234.255.255.255。

    多播的程序设计也要使用setsockopt()函数和getsockopt()函数来实现。其中对于setsockopt的第二个参数level不再是SOL_SOCKET,而是IPPROTO_IP;而且第三个参数optname常见的选项有:

    optname

    含 义

    IP_ADD_MEMBERSHIP

    在指定接口上加入组播组

    IP_DROP_MEMBERSHIP

    退出组播组

    选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP加入或者退出一个组播组,通过选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP,对一个结构struct ip_mreq类型的变量进行控制。

    struct ip_mreq原型如下:

    struct ip_mreq

    {

    struct in_addr    imr_multiaddr; /*加入或者退出的多播组IP地址*/

    struct in_addr    imr_interface; /*加入或者退出的网络接口IP地址,本机IP*/

    };

    选项IP_ADD_MEMBERSHIP用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播组接收数据。此选项的值为mreq结构,成员imr_multiaddr是需要加入的多播组IP地址,成员imr_interface是本机需要加入多播组的网络接口IP地址。例如:

    struct ip_mreq mreq;

    memset(&mreq, 0, sizeof(struct ip_mreq));

    mreq.imr_interface.s_addr = INADDR_ANY;

    mreq.imr_multiaddr.s_addr = inet_addr("224.1.1.1");

    if(-1 == setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)))

    {

    perror("setsockopt");

    exit(-1);

    }

    接下来再绑定组播的port号(如65000),就可以接收组播消息了。实例请参考《udp_组播》

    选项IP_ADD_MEMBERSHIP每次只能加入一个网络接口的IP地址到多播组,但并不是一个多播组仅允许一个主机IP地址加入,可以多次调用IP_ADD_MEMBERSHIP选项来实现多个IP地址加入同一个广播组,或者同一个IP地址加入多个广播组。

    选项IP_DROP_MEMBERSHIP用于从一个多播组中退出。例如:

    if(-1 == setsockopt(sfd, IPPROTP_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)))

    {

    perror("setsockopt");

    exit(-1);

    }

    展开全文
  • linux C编程实战

    2015-10-20 09:30:49
    目 录 第一篇 Linux和C编程基础  第1章 Linux系统概述 ...第三篇 Linux网络和图形界面编程  第11章 网络编程   第12章 GTK+图形界面编程  第四篇 Linux项目实践  第13章 项目实践:BT下载软件的开发
  • C++socket网络编程大全实战http服务器(支持php)视频培训教程概况:本课程会同时演示在linux和windows中的编程,课程中的线程和正则表达式都使用c++提供库。本课程包含了socket网络编程常用的所有特性,包括tcp、udp...
  • 第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...
  • Linux高级编程

    2018-08-13 23:40:58
    详细介绍了Linux下使用C语言开发进程、线程、网络编程(包括基于TCP和UDP的通讯的编程实现),以及循环服务器模式和并发服务器模式的具体实现。并给出了一个可以运行的C语言编写实现的聊天室的项目代码。
  • Linux网络编程5种实现(源代码)

    千次阅读 2019-08-07 18:11:44
    学习整理Linux网络编程5种服务器模型多线程、多进程、Select、Poll、Epoll实现源代码,注释较为详细,方便初学者学习。 1.简单阻塞式客户端服务器实现:(如若链接打不开,复制网址到浏览器打开) 源代码:...
  • 本课程是网络编程实践部分,带大家使用socket接口及其相关函数,从头编写一个服务器和客户端的通信程序,并且引出了应用层协议和业务逻辑的概念,本课程的目的是带领大家进入网络编程的世界,为大家后续的持续学习...
  • Linux在线词典小项目

    2017-10-28 19:30:04
    利用c语言,Linux网络编程,SQLite3数据库操作,文件操作
  • 基于linux编程龙书,Apue和UNP深入浅出讲解原理: 文件...linux基础编程linux网络编程ftpServer开源项目之源码解析 更多课程相关内容请查看我的博文:https://blog.csdn.net/wangwengx73sina/article/details/87940242
  • Linux高性能服务器编程》和《 Linux多线程服务端编程:使用muduo C ++网络库》的笔记和源码,以及两个轻量级服务器的项目代码。 GitHub: : 参考链接 raichen / LinuxServerCodes:Linux高级服务器编程源码 ...
  • 第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...
  • 第二部分对高性能服务器编程的核心要素进行了全面深入的剖析,包含Linux网络编程API、高级I/O函数、Linux服务器程序规范、高性能服务器程序框架、I/O复用、信号、定时器、高性能I/O框架库Libevent、多进程编程、多...
  • 基于Linux的C++之网络编程

    千次阅读 2018-05-15 16:16:08
    网络套接字示例:客户端 套接字的基本概念 通信类型: 控制套接字如何传输和处理数据,数据以包的形式传输 连接(connection)类型:确保所有包依序传输,如果丢包,则请求重传 数据报(datagram)类型:不...
  • linux下自创网络编程聊天室(2)

    千次阅读 2017-08-18 15:54:47
    总体设计 本聊天室系统采用了 c/s 形式。服务器主要是处理客户输入信息。...再有,在客户的聊天信息时,也要记录下客户的聊天记录,已备查看聊天记录所用。...其他功能见 linux下自创网络编程聊天室(3)
  • Linux C++网络编程

    千人学习 2019-01-17 09:52:40
    这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器的开发和架构工作。 这门课程学习难度颇高但也有着极其优渥的薪水(最少30K...
  • 第1章:文件io 第2章:文件属性 第3章:标准IO(C标准io库函数) 第4章:系统信息 第5章:进程环境(程序运行环境) 第6章:进程控制(程序是如何基于OS运行起来的) 第...C线程 第10章:高级IO 第11章:TCP/IP网络编程
  • 嵌入式LInux网络编程

    千人学习 2015-12-02 14:22:39
    本课程讲解网络编程基础知识,UDP与TCP网络编程步骤,基于多线程的网络聊天程序。
  • TCP/IP网络编程项目式教程(微课版)是由唐四薪编著、清华大学出版社于2019年11月出版的图书。 本书按照问题驱动、由浅入深的理念,以项目实例的形式介绍基于Visual C++的TCP/IP WinSock编程方法。 本书是微课版,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 127,144
精华内容 50,857
关键字:

linux网络编程小项目

linux 订阅