基于TCP/IP协议的多线程双向通信在OpenWrt上的实现

##1、TCP/IP协议组
TCP/IP协议(传输控制协议)由网络层的IP协议和传输层的TCP协议组成。

  • IP层负责网络主机的定位,数据传输的路由,由IP地址可以唯一的确定Internet上的一台主机。
  • TCP层负责面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象。

TCP/IP是个协议组,可分为三个层次:网络层、传输层和应用层:

  • 网络层:IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议;
  • 传输层:TCP协议与UDP协议;
  • 应用层:FTP、HTTP、TELNET、SMTP、DNS等协议;

这里写图片描述


##2、TCP特点:

  • TCP是面向连接的协议,通过三次握手建立连接,通讯完成时要拆除连接,由于TCP是面向连接协议,所以只能用于点对点的通讯。而且建立连接也需要消耗时间和开销。
  • TCP传输数据无大小限制,进行大数据传输。
  • TCP是一个可靠的协议,它能保证接收方能够完整正确地接收到发送方发送的全部数据。

TCP是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。


##3、HTTP

HTTP协议是建立在TCP协议之上的一种应用,HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立TCP连接,而且需要客户端向服务器发出请求后,请求中包含请求方法、URI、协议版本以及相关的MIME样式的消息,服务器端才能回复数据,包含消息的协议版本、一个成功和失败码以及相关的MIME式样的消息。在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

结论:HTTP是应用层协议,其传输都是被包装成TCP协议传输。可以用SOCKET实现HTTP。SOCKET是实现传输层协议的一种编程API,可以是TCP,也可以是UDP。


##4、Socket

Socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket,一个Socket由一个IP地址和一个端口号唯一确定。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。Socket是TCP/IP协议的一个十分流行的编程界面,但是,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。

1、套接字(socket)概念:

套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

简单地说,套接字就是网络进程的ID,网络通信归根到底是进程的通信,在网络中,每个节点有一个网络地址(即IP地址),两个进程通信时,首先要确定各自所在网络节点的网络地址,但是,网络地址只能确定进程所在的计算机,而一台计算机上可能同时有多个网络进程,还不能确定到底是其中的哪个进程,由此套接字中还要有其他的信息,那就是端口号(Port),在一台计算机中,一个端口一次只能分配给一个进程,即端口号与进程是一一对应的关系,所以,端口号和网络地址就能唯一地确定Internet中的一个网络进程。可以认为:套接字=网络地址+端口号。

2、Socket通讯过程:

服务端监听某个端口是否有连接请求,客户端向服务端发送连接请求,服务端收到连接请求向客户端发出接收消息,这样一个连接就建立起来了。客户端和服务端都可以相互发送消息与对方进行通讯。

3、建立socket连接:

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket。

4、套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

  • 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求;
  • 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  • 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

5、一个服务器如何为多个客户端并发服务

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

6、SOCKET连接与TCP连接

  • 创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
  • Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

【适用情况】
很多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给客户端;


##5、多线程双向通信在OpenWrt上的实现
注意:使用多线程链接库时,OpenWrt的Makefile文件的编写注意事项,请参考 此处

有关socket编程用到函数请参考 socket

1、TCP编程的客户端一般步骤:

1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选;
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选;
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;

2、TCP编程的服务器端一般步骤:

1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); * 可选;
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
8、关闭监听;

3、客户端源代码(thread_tcpclient.c):
功能介绍:
1、主线程里用于接收服务器发来的数据,子线程 ntid 用于扫描键盘输入,并发送给服务器端,实现双向通信。
2、通过发送字符 ‘p’ 到服务器端,服务器的接收子线程返回,并停止服务器的发送,进入监听状态。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

#define PORT 8082
#define MAXBUF 512

char send_buf[MAXBUF+1];
void *thread(void *x)  
{  
	int new_fd = *((int*)x);
	while(1)
	{
		bzero(send_buf, MAXBUF + 1);
 		scanf("%s",send_buf);
   		send(new_fd, send_buf, strlen(send_buf), 0); //第 4 步 向套接字中写入字符串
   	}
    return NULL;  
} 

char recv_buf[MAXBUF+1];
int main()
{
   int sockfd=socket(AF_INET,SOCK_STREAM,0);//第 1 步 创建一个体套接字
   struct sockaddr_in svraddr;//第 2 步 设置 addr 结构体
   svraddr.sin_family=AF_INET;//使用 internet 协议
   svraddr.sin_port=htons(PORT);
   inet_aton("192.168.10.1",&svraddr.sin_addr);
   connect(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr));//第 3 步 连接服务器
   
   pthread_t ntid;
   pthread_create(&ntid,NULL,thread,&sockfd);
    
   while(1)
   {
 	 bzero(recv_buf, MAXBUF + 1);/* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
 	 int len = recv(sockfd, recv_buf, MAXBUF, 0);/* 接收服务器来的消息 */
 	 if(len > 0)
		printf("接收消息成功:'%s',共%d个字节的数据\n", recv_buf, len);
	 else
   		printf("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
   }
 /* 关闭连接 */
 close(sockfd);
 return 0;
}

4、服务器端源代码(thread_tcpserver.c):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

#define PORT 8082
#define MAXBUF 512

char recv_buf[MAXBUF+1];
char link_state=1;
void* fun(void* x)
{
   //printf("enter thread!\r\n");
   pthread_detach(pthread_self());//非阻塞
   int new_fd=*((int*)x);
   while(1)
   {
     bzero(recv_buf, MAXBUF + 1);
   	 int len = recv(new_fd, recv_buf, MAXBUF, 0);
   	 if(recv_buf[0] == 'p')
   	 {
   		link_state = 0;
   		printf("------------客户端要断开!服务器别再发了!----------\r\n");
   		break;
   	 }
   }
   return NULL;
}

char send_buf[MAXBUF+1];
int newfd;
int inewfd=0;
int main()
{
   int sockfd=socket(AF_INET,SOCK_STREAM,0);//第 1 步 创建套接字
   struct sockaddr_in svraddr;//第 2 步 设置地址结构体
   svraddr.sin_family=AF_INET;//使用 internet 协议
   svraddr.sin_port=htons(PORT);
   inet_aton("0.0.0.0",&svraddr.sin_addr);
   int ret=bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr));//第 3 步 绑定
   if(ret<0)
   {
   	printf("error bind!\r\n");
   	exit(-1);
   }
   listen(sockfd,2);//第 4 步 监听,最多两个client
   while(1)
   {
   	 newfd=accept(sockfd,NULL,NULL); //第 5 步 接收
   	 pthread_t ntid;
     pthread_create(&ntid,NULL,fun,&newfd); 
	 while(1)
     {
     	if(link_state == 0)
     		break;
     	bzero(send_buf, MAXBUF + 1);
	 	strcpy(send_buf, "hello,my client!\n");
     	int len = send(newfd, send_buf, strlen(send_buf), 0);//发消息给客户端
	 	if(len < 0) 
			printf("消息发送失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno));
		 else 
	 		printf("消息发送成功,共发送了%d个字节!\n", len);
   	 	sleep(1);
     }
     link_state = 1;
   }
 /* 关闭连接 */
 close(sockfd);
 return 0;
}

参考文章:
[0] 聊聊Socket、TCP/IP、HTTP、FTP及网络编程
[1] socket–目录清晰
[2] Tcp多线程服务器和客户端程序
[3] linux 网络编程:客户端与服务器通过TCP协议相互通信 + UDP
[4] tcp 服务端如何判断客户端断开连接
[5] socket选项自带的TCP异常断开检测
[6] 服务器中判断客户端socket断开连接的方法
[7]计算机网络学习笔记–OSI七层体系结构
[8]基于Linux操作系统下的TCP/IP网络通信研究与应用

由于篇幅有限,有关多线程的知识将在下次博客中介绍。

																 @本文作者:LeatherWang
  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值