Socket编程基础

来源:互联网 发布:2016淘宝新店太难做了 编辑:程序博客网 时间:2024/06/06 03:34

TCP/IP 是因特网的通信协议。

TCP/IP 是用于因特网 (Internet) 的通信协议。通信协议是对计算机必须遵守的规则的描述,只有遵守这些规则,计算机之间才能进行通信。

 

浏览器和服务器都在使用 TCP/IP

因特网浏览器和因特网服务器均使用 TCP/IP 来连接因特网。浏览器使用 TCP/IP 来访问因特网服务器,服务器使用 TCP/IP 向浏览器传回 HTML。

 

因特网地址也是 TCP/IP

你的因特网地址 61.181.156.187 也是标准的 TCP/IP 协议的一部分。

 

计算机通信协议

计算机通信协议是对那些计算机必须遵守以便彼此通信的的规则的描述。

 

什么是 TCP/IP

TCP/IP 是供已连接因特网的计算机进行通信的通信协议。

TCP/IP 指传输控制协议/网际协议 (Transmission Control Protocol / Internet Protocol)。

TCP/IP 定义了电子设备(比如计算机)如何连入因特网,以及数据如何在它们之间传输的标准。因特网在全球范围,由采用TCP/IP协议族的众多计算机网相互连接而成的最大的开放式计算机网络

 

在 TCP/IP 内部

在 TCP/IP 中包含一系列用于处理数据通信的协议:

· TCP (传输控制协议) - 应用程序之间通信

· UDP (用户数据包协议) - 应用程序之间的简单通信

· IP (网际协议) - 计算机之间的通信

· ICMP (因特网消息控制协议) - 针对错误和状态

· DHCP (动态主机配置协议) - 针对动态寻址

TCP 使用固定的连接

TCP 用于应用程序之间的通信。

当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。

这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。

UDP 和 TCP 很相似,但是更简单,同时可靠性低于 TCP。

 

IP 是无连接的

IP 用于计算机之间的通信。

IP 是无连接的通信协议。它不会占用两个正在通信的计算机之间的通信线路。这样,IP 就降低了对网络线路的需求。每条线可以同时满足许多不同的计算机之间的通信需要。

通过 IP,消息(或者其他数据)被分割为小的独立的包,并通过因特网在计算机之间传送。

IP 负责将每个包路由至它的目的地。

 

IP 路由器

当一个 IP 包从一台计算机被发送,它会到达一个 IP 路由器。

IP 路由器负责将这个包路由至它的目的地,直接地或者通过其他的路由器。

在一个相同的通信中,一个包所经由的路径可能会和其他的包不同。而路由器负责根据通信量、网络中的错误或者其他参数来进行正确地寻址。

 

TCP/IP

TCP/IP 意味着 TCP 和 IP 在一起协同工作。

TCP 负责应用软件(比如你的浏览器)和网络软件之间的通信。

IP 负责计算机之间的通信。

TCP 负责将数据分割并装入 IP 包,然后在它们到达的时候重新组合它们。

IP 负责将包发送至接受者。

 

IP地址

每个计算机必须有一个 IP 地址才能够连入因特网。

每个 IP 包必须有一个地址才能够发送到另一台计算机。

 

TCP/IP 是不同的通信协议的大集合。

 

协议族

TCP/IP 是基于 TCP 和 IP 这两个最初的协议之上的不同的通信协议的大的集合。由于TCP/IP最有名、有用,故命名为TCP/IP协议族。

 

TCP - 传输控制协议

TCP 用于从应用程序到网络的数据传输控制。

TCP 负责在数据传送之前将它们分割为 IP 包,然后在它们到达的时候将它们重组。

 

IP - 网际协议

IP 负责计算机之间的通信。

IP 负责在因特网上发送和接收数据包。

 

HTTP - 超文本传输协议

HTTP 负责 web 服务器与 web 浏览器之间的通信。

HTTP 用于从 web 客户端(浏览器)向 web 服务器发送请求,并从 web 服务器向 web 客户端返回内容(网页)。

 

HTTPS - 安全的 HTTP

HTTPS 负责在 web 服务器和 web 浏览器之间的安全通信。

作为有代表性的应用,HTTPS 会用于处理信用卡交易和其他的敏感数据。

 

TCP/IP协议中各层对应的协议

应用层:文件传输:TFTP,FTP,NFS

E-MAILSMTP

远程登录:TELNET,RLOGIN

网络管理:SNMP

名称管理:DNS

传输层:TCP,UDP

 

知名端口号:

FTP:21

TELNET:23

SMTP:25

DNS:53

TFTP:69

SNMP:161

RIP:520

 

Socket

ServerSocket服务器端是可以接受多个客户端的连接的,也就是说默认是支持多线程的。但是在处理客户端数据的时候只能为一个客户端服务,必须这个客户端断开连接之后,才会为另一个连着的客户端服务,要能处理多线程的ServerSocket需要手动编写多线程的代码,见Thinking in Java部分。

 

当客户端Socket socket = new Socket("localhost",8001);服务器端Socket socket = serverSocket.accept();之后,连接开始被建立。

侦听套接字(ServerSocket)只能接收新的连接请求,不能接收实际的数据包。实际的数据包是Socket通过流来得到的。

在命令行窗口中执行netstat可以看见:  

服务器端的程序占据的端口:

  

客户端的程序发送数据占据的端口:       

     

在客户端程序运行完毕后(即客户端的Socket关闭)一段时间,系统自动释放此两个端口 ,即服务器端也会清除此客户端,连接失效;在多个客户端连接服务器端时,每一个连接都会有一组这样的端口(服务器端的端口是不会改变的)

serverSocket.accept()监听客户端的连接。

 

通过输入流读取客户端数据:

方法一:

while (inputStream.available() > 0) {      System.out.println(inputStream.read());

}

available()方法不受阻塞地从此输入流读取(或跳过)的估计字节数

 

方法二:

int value = -1;

while ((value = inputStream.read()) != -1) {

System.out.println(value);

}

read()在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞 

所以在客户端连接或者输出流不关闭的情况下,此种方式是一直阻塞的。 

 

serverSocket.setSoTimeout(1000);

设置连接超时,即serverSocket.accept()等待的时间。默认的是永不超时。

 

客户端有可能会接收不到客户端数据的情况(对于availableread的使用):

http://topic.csdn.net/u/20100927/13/96e6678f-1319-441f-a23c-0e3696fbe1f6.html?1416152729

 

SO_KEEPALIVE 选项(检测连接是否有效,并非连接是否断开)

 

设置该选项: public void setKeepAlive(boolean on) throws SocketException
读取该选项: public boolean getKeepAlive() throws SocketException 

 

当 SO_KEEPALIVE 选项为 true 表示底层的TCP 实现会监视该连接是否有效当连接处于空闲状态(连接的两端没有互相传送数据超过了 小时时本地的TCP 实现会发送一个数据包给远程的 Socket. 如果远程Socket 没有发回响应, TCP实现就会持续尝试 11 分钟直到接收到响应为止如果在 12 分钟内未收到响应, TCP 实现就会自动关闭本地Socket, 断开连接在不同的网络平台上, TCP实现尝试与远程Socket 对话的时限有所差别.

SO_KEEPALIVE 选项的默认值为 false, 表示TCP 不会监视连接是否有效不活动的客户端可能会永远存在下去而不会注意到服务器已经崩溃.

 

以下代码把 SO_KEEPALIVE 选项设为 true:

if(!socket.getKeepAlive()) socket.setKeepAlive(true);

 

测试代码:

服务器端代码一:

ServerSocket serverSocket = new ServerSocket(8001);

while (true) {

Socket socket = serverSocket.accept();

inputStream = socket.getInputStream();  

int value = -1;

while ((value = inputStream.read()) != -1) {

System.out.println(value);

}

}

服务器端代码二:

try {

ServerSocket serverSocket = new ServerSocket(8001);

InputStream inputStream = null;

while (true) {

Socket socket = serverSocket.accept();

try {

Thread.sleep(1000);

catch (InterruptedException e) {

e.printStackTrace();

}

inputStream = socket.getInputStream();

while (inputStream.available() > 0) {     System.out.println(inputStream.read());

}

}

catch (IOException e) {

e.printStackTrace();

}

 

客户端代码:

try {

Socket socket = new Socket("localhost",8001);

Thread.sleep(1000);

outputStream = socket.getOutputStream();

outputStream.write('b');

outputStream.flush();

catch (UnknownHostException e) {

e.printStackTrace();

catch (IOException e) {

e.printStackTrace();

finally {

try {

outputStream.close();

catch (IOException e) {

e.printStackTrace();

}

}

 

 

Thinking in Java关键点

创建一个ServerSocket时,只需为其赋予一个端口编号。不必把一个IP地址分配它,因为它已经在自己代表的那台机器上了。但在创建一个Socket时,却必须同时赋予IP地址以及要连接的端口编号(另一方面,从ServerSocket.accept()返回的Socket已经包含了所有这些信息)。

 

处理多线程的ServerSocket:

ServerSocket可以正常工作,但每次只能为一个客户程序提供服务。在典型的服务器中,我们希望同时能处理多个客户的请求。解决这个问题的关键就是多线程处理机制。

最基本的方法是在服务器(程序)里创建单个ServerSocket,并调用accept()来等候一个新连接。一旦accept()返回,我们就取得结果获得的Socket,并用它新建一个线程,令其只为那个特定的客户服务。然后再调用accept(),等候下一次新的连接请求。

 

UDP:

TCP具有非常高的开销。

还有另一种协议,名为“用户数据报协议”(UDP),它并不刻意追求数据包会完全发送出去,也不能担保它们抵达的顺序与它们发出时一样。我们认为这是一种“不可靠协议”(TCP当然是“可靠协议”)。听起来似乎很糟,但由于它的速度快得多,所以经常还是有用武之地的。对某些应用来说,比如声音信号的传输,如果少量数据包在半路上丢失了,那么用不着太在意,因为传输的速度显得更重要一些。大多数互联网游戏,如Diablo,采用的也是UDP协议通信,因为网络通信的快慢是游戏是否流畅的决定性因素。也可以想想一台报时服务器,如果某条消息丢失了,那么也真的不必过份紧张。另外,有些应用也许能向服务器传回一条UDP消息,以便以后能够恢复。如果在适当的时间里没有响应,消息就会丢失。

对数据报来说,我们在客户和服务器程序都可以放置一个DatagramSocket(数据报套接字),但与ServerSocket不同,前者不会干巴巴地等待建立一个连接的请求。这是由于不再存在“连接”,取而代之的是一个数据报陈列出来。另一项本质的区别的是对TCP套接字来说,一旦我们建好了连接,便不再需要关心谁向谁“说话”——只需通过会话流来回传送数据即可。但对数据报来说,它的数据包必须知道自己来自何处,以及打算去哪里。这意味着我们必须知道每个数据报包的这些信息,否则信息就不能正常地传递。

DatagramSocket用于收发数据包,而DatagramPacket包含了具体的信息。准备接收一个数据报时,只需提供一个缓冲区,以便安置接收到的数据。数据包抵达时,通过DatagramSocket,作为信息起源地的因特网地址以及端口编号会自动得到初化。所以一个用于接收数据报的DatagramPacket构建器是:

DatagramPacket(buf, buf.length)

其中,buf是一个字节数组。既然buf是个数组,大家可能会奇怪为什么构建器自己不能调查出数组的长度呢?实际上我也有同感,唯一能猜到的原因就是C风格的编程使然,那里的数组不能自己告诉我们它有多大。

可以重复使用数据报的接收代码,不必每次都建一个新的。每次用它的时候(再生),缓冲区内的数据都会被覆盖。

缓冲区的最大容量仅受限于允许的数据报包大小,这个限制位于比64KB稍小的地方。但在许多应用程序中,我们都宁愿它变得还要小一些,特别是在发送数据的时候。具体选择的数据包大小取决于应用程序的特定要求。

发出一个数据报时,DatagramPacket不仅需要包含正式的数据,也要包含因特网地址以及端口号,以决定它的目的地。所以用于输出DatagramPacket的构建器是:

DatagramPacket(buf, length, inetAddress, port)

这一次,buf(一个字节数组)已经包含了我们想发出的数据。length可以是buf的长度,但也可以更短一些,意味着我们只想发出那么多的字节。另两个参数分别代表数据包要到达的因特网地址以及目标机器的一个目标端口(注释②)。

 

②:我们认为TCP和UDP端口是相互独立的。也就是说,可以在端口8080同时运行一个TCP和UDP服务程序,两者之间不会产生冲突。

 

大家也许认为两个构建器创建了两个不同的对象:一个用于接收数据报,另一个用于发送它们。如果是好的面向对象的设计方案,会建议把它们创建成两个不同的类,而不是具有不同的行为的一个类(具体行为取决于我们如何构建对象)。这也许会成为一个严重的问题,但幸运的是,DatagramPacket的使用相当简单,我们不需要在这个问题上纠缠不清。