目录结构:

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. Shell教程 之echo命令

    1.显示普通字符串 这里的双引号完全可以省略,以下命令效果一致: echo "传递参数实例!" echo 传递参数实例! 2.显示转义字符 echo "\"传递 ...

  2. Jasper打印示例

    import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List ...

  3. 【centos】centos中添加一个新用户,并授权

    前言 有时候给root用户不太方便,新建一个用于并赋予权限这个做法相对好些 创建新用户 创建一个用户名为:cmj [root@localhost ~]# adduser cmj 为这个用户初始化密码, ...

  4. js中json知识点

    首先,json是一种数据格式,而不能说是一种对象(object).这一点是非常重要的. 起源是不同的语言中数据对象的形式是不一样的,我们为了在不同的语言中传递数据,发明了一种json格式用于消除这种差 ...

  5. Bootstrap(9) 巨幕页头缩略图和警告框组件

    一.巨幕组件巨幕组件主要是展示网站的关键性区域.//在固定的范围内,有圆角 <div class="container"> <div class="ju ...

  6. 我的第一个WCF程序

    写WCF,VS需要一管理员身份呢启动,否则服务无法访问. model层 using System; using System.Runtime.Serialization; namespace MyMo ...

  7. 深入理解HTTP协议及原理分析

    1. 基础概念篇 1.1 介绍 HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写.它的发展是万维网协会(World Wide Web Consortium)和 ...

  8. (转)easyui datagrid 部分参数说明

    easyui datagrid 部分参数 数据表格属性(DataGrid Properties) 属性继承控制面板,以下是数据表格独有的属性. 名称 类型 描述 默认值 columns array 数 ...

  9. 789A Anastasia and pebbles

    A. Anastasia and pebbles time limit per test 1 second memory limit per test 256 megabytes input stan ...

  10. JS 实现 jQuery的$(function(){});

    1.浏览器渲染引擎的HTML解析流程 何谓“渲染”,其实就是浏览器把请求到的HTML内容显示出来的过程.渲染引擎首先通过网络获得所请求文档的内容,通常以8K分块的方式完成.下面是渲染引擎在取得内容之后 ...