目录结构:

contents structure [-]

TCP和UDP协议都是运行在传输层的协议,在OSI网络的七层传输模型中,如果我们把应用层、表示层、传输层统称为应用层(其实在TCP/IP模型中就是把应用层、表示层、传输层统称为应用层的),那么我们平时编写的程序就属于应用层。应用层位于传输层之上,当我们需要使用TCP/UDP协议的时候,直接调用传输层留下的TCP/UDP协议接口就可以了。在下面的示例中,我们把发送发送数据的一方称为客户端,接受数据的一方称为服务端。下面TCP和UDP的实现都采用了C#和Java代码,这个笔者还需要提一点,就是在Android中使用连接的时候,由于为了防止网速太卡,所以Android中使用TCP连接的时候,应该在一个新建的线程中进行。

1 TCP协议和UDP协议的比较

1.1 TCP协议

TCP的全称是Transmission Control Protocol (传输控制协议)

  • 传输控制协议,是一种面向连接的协议,类似打电话
  • 在通信的整个过程中保持连接
  • 保证了数据传递的可靠性和有序性
  • 是一种全双工的字节流通信方式
  • 服务器压力比较大,资源消耗比较快,发送数据效率比较低
  • 点对点的传输协议

接下来笔者解释一下上面的几个概念:

面向连接的传输协议:面向连接,比如A打电话给B,如果B接听了,那么A和B之间就的通话,就是面向连接的。

可靠的传输协议:可靠的,一旦建立了连接,数据的发送一定能够到达,并且如果A说“你好吗?” B不会听到“吗你好”,这就是可靠地数据传输。

双全工的传输协议:全双工,这个理解起来也很简单,A打电话给B,B接听电话,那么A可以说话给B听,同样B也可以给A说话,不可能只允许一个人说话.。

点对点的传输协议:点对点,这个看了上面的举例相比大家都知道了,还要说一点的是,如果在A和B打电话过程中,B又来了一个紧急电话,那么B就要将与A的通话进行通话保持,所以不管怎么讲同一个连接只能是点对点的,不能一对多。

1.2 UDP协议

UDP是User Datagram Protocol(用户数据报协议)

  • 用户数据报协议,是一种非面向连接的协议,类似写信
  • 在通信的整个过程中不需要保持连接
  • 不保证数据传输的可靠性和有序性
  • 是一种双全工的数据报通信方式
  • 服务器压力比较小,资源比较低,发送效率比较高
  • 可以一对一、一对多、多对一、多对多

2 基于TCP的网络编程模型

2.1 使用Java代码实现TCP

服务端:

  • 创建ServerSocket的对象并且提供端口号, public ServerSocket(int port)
  • 等待客户端的请求连接,使用accept()方法, public Socket accept()
  • 连接成功后,使用Socket得到输入流和输入流,进行通信
  • 关闭相关资源

客户端:

  • 创建Socket类型的对象,并且提供IP地址和端口号, public Socket(String host, int port)
  • 使用Socket构造输入流和输出流进行通信
  • 关闭相关资源

下面这个例子

 /*
* 在提供端口号的时候应该注意:最好定义在1024~49151。
*/
public class TestServerString { public static void main(String[] args) {
try{
//1.创建ServerSocket类型的对象,并提供端口号
ServerSocket ss = new ServerSocket(8888);
//2.等待客户端的连接请求,使用accept()方法,保持阻塞状态
while(true){
System.out.println("等待客户端的连接请求...");
Socket s = ss.accept();
new ServerThread(s).start();
System.out.println("客户端连接成功!");
} }catch(Exception e){
e.printStackTrace();
} } }

TestServerString类

在TestServerString类中采用循环相应多个客户端的连接,

 public class ServerThread extends Thread{

     private Socket s;

     public ServerThread(Socket s){
this.s=s;
} @Override
public void run(){
try{
BufferedReader br = new BufferedReader(
new InputStreamReader(s.getInputStream()));
PrintStream ps = new PrintStream(s.getOutputStream());
//编程实现服务器可以不断地客户端进行通信
while(true){
//服务器接收客户端发来的消息并打印
String str = br.readLine();
//当客户端发来"bye"时,结束循环
if("bye".equalsIgnoreCase(str)) break;
System.out.println(s.getLocalAddress()+":"+ str);
//向客户端回发消息“I received!”
ps.println("server received!");
}
//4.关闭相关的套接字
ps.close();
br.close();
s.close();
}catch(Exception e){
e.printStackTrace();
}
}
}

ServerThread

在输入流和输出流中采用循环可客户端传输信息

 public class TestClientString {

     public static void main(String[] args) {

         try{
//1.创建Socket类型的对象,并指定IP地址和端口号
Socket s = new Socket("127.0.0.1", 8888);
//2.使用输入输出流进行通信
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
PrintStream ps = new PrintStream(s.getOutputStream());
BufferedReader br2 = new BufferedReader(
new InputStreamReader(s.getInputStream()));
//编程实现客户端不断地和服务器进行通信
while(true){
//提示用户输入要发送的内容
System.out.println("请输入要发送的内容:");
String msg = br.readLine();
ps.println(msg);
//当客户端发送"bye"时,结束循环
if("bye".equalsIgnoreCase(msg)){
break;
};
//等待接收服务器的回复,并打印回复的结果
String str2 = br2.readLine();
System.out.println("服务器发来的消息是:" + str2);
}
//3.关闭Socket对象
br2.close();
br.close();
ps.close();
s.close();
}catch(Exception e){
e.printStackTrace();
} } }

TestClientString

在客户端中采用循环,可以让客户端与服务器建立一次连接,实现多次通信。

在socket中有两个构造方法,值得提一下:

Socket(InetAddress address, int port)

使用这个构造方法,程序会自动绑定一个本地地址,并且在以后的连接中不会改变,如果需要在本地模拟多个客户端,那么就不可用了。

下面这个构造方法,在连接到远程地址中可以指定本地地址和端口:

Socket(String host, int port, InetAddress localAddr, int localPort)

如果本地端口指定为0,那么系统将会自动选择一个空闲的端口绑定。

2.2 使用C#代码实现TCP

服务端:

  • 指定需要监听的地址
  • 指定需要监听的端口
  • 开始监听
  • 获取TcpClient实例
  • 获取NetworkStream实例
  • 传输数据
  • 关闭流
  • 关闭连接

客户端:

  • 指明目的地的地址
  • 指明目的地的端口
  • 连接
  • 获取NetworkStream对象
  • 传输数据
  • 关闭流
  • 关闭连接

下面笔者给出一个用户服务端和客户端互发一条消息的示例:

服务端代码:

    class Server
{
static void Main(string[] args)
{
IPAddress ip = IPAddress.Parse("127.0.0.1");
TcpListener server = new TcpListener(ip,);
server.Start();
TcpClient client = server.AcceptTcpClient(); NetworkStream dataStream = client.GetStream();
//读数据
byte[] buffer = new byte[];
int dataSize = dataStream.Read(buffer, , );
Console.WriteLine("server读取到数据:"+Encoding.Default.GetString(buffer,,dataSize)); //写数据
string msg = "你好 client";
byte[] writebuffer = Encoding.Default.GetBytes(msg);
dataStream.Write(writebuffer, , writebuffer.Length); dataStream.Close();
client.Close(); Console.ReadLine();
}
}

Server.cs

客户端代码:

    class Client
{
static void Main(string[] args)
{
IPAddress ip = IPAddress.Parse("127.0.0.1");
TcpClient client = new TcpClient();
client.Connect(ip, ); //写数据
NetworkStream dataStream = client.GetStream();
string msg = "你好 server";
byte[] buffer = Encoding.Default.GetBytes(msg);
dataStream.Write(buffer, , buffer.Length); //读数据
byte[] readbuffer = new byte[];
int dataSize = dataStream.Read(readbuffer, , );
Console.WriteLine("Client读取到数据:" + Encoding.Default.GetString(readbuffer, , dataSize)); dataStream.Close();
client.Close();
Console.ReadLine();
}
}

Client

3 基于UDP的网络编程模型

3.1 使用Java代码实现UDP

客户端:

  • 创建DatagramSocket类型的对象,不需要提供任何信息, public DatagramSocket()
  • 创建DatagramPacket类型的对象,指定发送的内容、IP地址、端口号, public DatagramPacket(byte[] buf, int length, InetAddress address, int port)
  • 发送数据,使用send()方法, public void send(DatagramPacket p)
  • 关闭相关的资源

服务端:

  • 创建DatagramSocket类型的对象,并且指定端口, public DatagramSocket(int port)
  • 创建DatagramPacket类型的对象,用于接收发来的数据, public DatagramPacket(byte[] buf, int length)
  • 接收数据,使用receive()方法, public void receive(DatagramPacket p)
  • 关闭相关资源

例:

发送方:

 public class UDPSender {

     public static void main(String[] args) {
try{
/*
* create DatagramSocket instance
*/
DatagramSocket ds=new DatagramSocket();
//create DatagramPackage instance and specify the content to send ,ip address,port
InetAddress ia=InetAddress.getLocalHost();
System.out.println(ia.toString());
String str="吴兴国";
byte[] data=str.getBytes();
DatagramPacket dp=new DatagramPacket(data,data.length,ia,8888);
//send data use send()
ds.send(dp);
//create DatagramPacket instance for receive
byte []b2=new byte[1024];
DatagramPacket dp2=new DatagramPacket(b2,b2.length);
ds.receive(dp2);
System.out.println("result:"+new String(data));
//close resorce
ds.close();
}catch(IOException e){
e.printStackTrace();
}
} }

UDPSender

接收方:

 public class UDPReceiver {

     public static void main(String[] args) {
try{
/*
* create DatagramSocket instance,and support port
*/
DatagramSocket ds=new DatagramSocket(8888);
/*
* create DatagramPackage instance for receive data
*/
byte []data=new byte[1024];
DatagramPacket dp=new DatagramPacket(data,data.length);
/*
* receive source
*/
ds.receive(dp);
System.out.println("contents are:"+new String(data,0,dp.getLength()));
/*
* send data
*/
String str="I received!";
byte[] b2=str.getBytes();
DatagramPacket dp2=
new DatagramPacket(b2,b2.length,dp.getAddress(),dp.getPort());
ds.send(dp2);
System.out.println("发送成功,ip:"+dp.getAddress()); /*
* close resource
*/
}catch(SocketException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
} }

UDPReceiver

3.2 使用C#代码实现UDP

客户端:

  • 实例化一个客户端的IpEndPoint对象
  • 实例化一个客户端的UdpClient对象
  • 实例化服务端的IpEndPoint对象
  • 使用客户端的UdpClient发送数据到服务端

服务端:

  • 实例化一个服务端的IpEndPoint对象
  • 实例化一个服务端的IpUdpClient对象
  • 实例化客户端的的IpEndPoint
  • 使用服务端的IpUdpClient接受数据

下面是一个案例,实现客户端向服务端发送一条信息,然后服务端接收信息并且打印出来:

服务端:

    class Server
{
static void Main(string[] args)
{
IPEndPoint udpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), );
UdpClient udpClient = new UdpClient(udpPoint);
//IPEndPoint senderPoint = new IPEndPoint(IPAddress.Parse("14.55.36.2"), 0);
IPEndPoint senderPoint = new IPEndPoint(IPAddress.Any, );
byte[] recvData = udpClient.Receive(ref senderPoint);
Console.WriteLine("Receive Message:{0}", Encoding.Default.GetString(recvData));
Console.Read();
}
}

Server.cs

客户端:

    class Client
{
static void Main(string[] args)
{
IPEndPoint udpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), );//实例化本地IPEndPoint
UdpClient udpClient = new UdpClient(udpPoint);//实例化本地UpdClient
//UdpClient udpClient = new UdpClient();
string sendMsg = "Hello UDP Server.";
byte[] sendData = Encoding.Default.GetBytes(sendMsg);
IPEndPoint targetPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), );//服务端的IPEndPoint对象
udpClient.Send(sendData, sendData.Length, targetPoint);
Console.WriteLine("Send Message:{0}", sendMsg);
Console.Read();
}
}

Client.cs

4 TCP的长连接和短连接

CP协议中有长连接和短连接之分。短连接在数据包发送完成后就会自己断开,长连接在发包完毕后,会在一定的时间内保持连接,即我们通常所说的Keepalive(存活定时器)功能。

默认的Keepalive超时需要7,200,000 milliseconds,即2小时,探测次数为5次。它的功效和用户自己实现的心跳机制是一样的。开启Keepalive功能需要消耗额外的宽带和流量,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,Keepalive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接。

keepalive并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:
(1)在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped),
(2)它们消费了不必要的宽带,
(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器。

一些服务器应用程序可能代表客户端占用资源,它们需要知道客户端主机是否崩溃。存活定时器可以为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。
个人计算机用户使用TCP/IP协议通过Telnet登录一台主机,这是能够说明需要使用存活定时器的一个常用例子。如果某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的连接。如果客户端消失,留给了服务器端半打开的连接,并且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开连接。
也可以在客户端设置存活器选项,且没有不允许这样做的理由,但通常设置在服务器。如果连接两端都需要探测对方是否消失,那么就可以在两端同时设置(比如NFS)。

keepalive工作原理:
若在一个给定连接上,两小时之内无任何活动,服务器便向客户端发送一个探测段。(我们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:
1) 客户端主机依旧活跃(up)运行,并且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,如果在这两个小时到期之前,连接上发生应用程序的通信,则定时器重新为往下的两小时复位,并且接着交换数据。
2) 客户端已经崩溃,或者已经关闭(down),或者正在重启过程中。在这两种情况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,并且在75秒之后超时。服务器将总共发送10个这样的探测,每个探测75秒。如果没有收到一个响应,它就认为客户端主机已经关闭并终止连接。
3) 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。
4) 客户端主机活跃运行,但从服务器不可到达。这与状态2类似,因为TCP无法区别它们两个。它所能表明的仅是未收到对其探测的回复。

服务器不必担心客户端主机被关闭然后重启的情况(这里指的是操作员执行的正常关闭,而不是主机的崩溃)。
当系统被操作员关闭时,所有的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在连接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以允许服务器检测这种状态。
在第一种状态下,服务器应用程序不知道存活探测是否发生。凡事都是由TCP层处理的,存活探测对应用程序透明,直到后面2,3,4三种状态发生。在这三种状态下,通过服务器的TCP,返回给服务器应用程序错误信息。(通常服务器向网络发出一个读请求,等待客户端的数据。如果存活特征返回一个错误信息,则将该信息作为读操作的返回值返回给服务器。)在状态2,错误信息类似于“连接超时”。状态3则为“连接被对方复位”。第四种状态看起来像连接超时,或者根据是否收到与该连接相关的ICMP错误信息,而可能返回其它的错误信息。

在TCP程序中,我经常需要确认客户端和服务端是否还保持者连接,这个时候有如下两种方案:
1.TCP连接双方定时发握手消息,并且在后面的程序中单独启线程,发送心跳信息。

2.利用TCP协议栈中的KeepAlive探测,也就是对TCP的连接的Socket设置KeepAlive。

在Java中利用下面的方法设置长连接:

setKeepAlive(boolean)

在C#可以按照如下方式设置长连接:

SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true)

5 网络编程中定义端口应注意事项

互联网中的协议被分为三种,

  • 众所周知(Well Known Ports)端口:编号0~1023,通常由操作系统分配,用于标识一些众所周知的服务。众所周知的端口编号通常又IANA统一分配。它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。
  • 注册(Registered Ports)端口:编号1024~49151,可以动态的分配给不同的网络应用进程。
  • 动态和/或私有端口(Dynamic and/or Private Ports):编号49152~65535,理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。但也有例外:SUN的RPC端口从32768开始。

6 参考文章:

C#通信示例

因特网中端口

TCP与UDP传输协议的更多相关文章

  1. 一、网络编程-UDP传输协议及socket套接字使用

    知识点基本介绍:1.网络通信协议一般就是UDP和TCP俩种传输协议,这一章先说UDP,UDP是一种比较简单的传输协议,如qq使用的就是UDP          2.ip:ip就是标记网络中中的一台电脑 ...

  2. Android开发:如何实现TCP和UDP传输

    TCP和UDP在网络传输中非常重要,在Android开发中同样重要. 首先来看一下什么是TCP和UDP. 什么是TCP? TCP:Transmission Control Protocol 传输控制协 ...

  3. Android如何实现TCP和UDP传输

    TCP和UDP在网络传输中非常重要,在Android开发中同样重要. 首先我们来看一下什么是TCP和UDP. 什么是TCP? TCP:Transmission Control Protocol 传输控 ...

  4. 基于UDP传输协议局域网文件接收软件设计 Java版

    网路传输主要的两大协议为TCP/IP协议和UDP协议,本文主要介绍基于UDP传输的一个小软件分享,针对于Java网络初学者是一个很好的练笔,大家可以参考进行相关的联系,但愿能够帮助到大家. 话不多说, ...

  5. Android中Socket通信之TCP与UDP传输原理

    一.Socket通信简介 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是"请求-响应方式",即在请求时 ...

  6. TCP(控制传输协议)详解

    1.传输层概述 在OSI参考模型中,网络层是面向通信的最高层但同时也是面向用户程序的最底层. 传输层的主要作用: 复用:在发送端,多个应用程序公用一个传输层: 分用:在接收端,传输层把从网络层接收到的 ...

  7. 轨迹系列——Socket总结及实现基于TCP或UDP的809协议方法

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 在上一篇博客中我详细介绍了809协议的内容.809协议规范了通 ...

  8. 轨迹系列7——Socket总结及实现基于TCP或UDP的809协议方法

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 在上一篇博客中我详细介绍了809协议的内容.809协议规范了通 ...

  9. 传输层协议TCP和UDP分析

    分析所用软件下载:Wireshark-win32-1.10.2.exe 阅读导览 1. 分析应用TCP协议,以及TCP链接管理 2. 分析应用UDP协议 分析要求 (1)TCP部分: 学习3CDaem ...

随机推荐

  1. SML + NL + HJ

    Join是一种试图将两个表结合在一起的谓词,一次只能连接2个表,表连接也可以被称为表关联.在后面的叙述中,我们将会使用”row source”来代替”表”,因为使用row source更严谨一些,并且 ...

  2. linux 升级python2.7

    linux为centos6,系统默认安装了python2.6,需要执行的python脚本内容包含标准库之xml.etree.ElementTree  用到库里的一个iter方法是python2.7的新 ...

  3. overflow属性的用法

    <style type="text/css">div{ background-color:#00FFFF; width:150px; height:150px; ove ...

  4. poj1019(打表预处理+数学)

    题目链接:http://poj.org/problem?id=1019 题意:对于序列1121231234...,求第i个数字(i<=2147483647). 思路:记第一组为1,第二组为12, ...

  5. poj1308(并查集)

    题目链接:http://poj.org/problem;jsessionid=436A34AE4BE856FB2DF9B264DCA9AA4E?id=1308 题意:给定一些边让你判断是否构成数. 思 ...

  6. Numpy 数组操作

    Numpy 数组操作 Numpy 中包含了一些函数用于处理数组,大概可分为以下几类: 修改数组形状 翻转数组 修改数组维度 连接数组 分割数组 数组元素的添加与删除 修改数组形状 函数 描述 resh ...

  7. Mac mysql sql_model引起的问题

    问题: 我这里时应为timestamp引起的,服务器的数据使用的mysql5.本地使用的是mysql8,sql_model 不同导致数据不能够在数据库中添加. 解决: 在/etc/下查找my.cnf文 ...

  8. 一个先进的App框架:使用Ionic创建一个简单的APP

    原文  http://www.w3cplus.com/mobile/building-simple-app-using-ionic-advanced-html5-mobile-app-framewor ...

  9. c++ sizeof,strlen, length

    #include <map>#include <iostream>#include <algorithm>#include <functional>#i ...

  10. php5.6 版本出现 Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a future version 的错误

    解决方法是修改php.ini配置: ;always_populate_raw_post_data = -1 把前面的分号去掉 always_populate_raw_post_data = -1 然后 ...