- 在现实网络传输应用中,
通常使用TCP、IP或UDP这3种协议实现数据传输。
在传输数据的过程中,
需要通过一个双向的通信连接实现数据的交互。
在这个传输过程中,
通常将这个双向链路的一端称为Socket,
一个Socket通常由一个IP地址和一个端口号来确定。
在整个数据传输过程中,Socket的作用是巨大的。
在Java编程应用中,Socket是Java网络编程的核心。
Socket基础
- 在
网络编程中有两个主要的问题,
一个是如何准确地定位网络上一台或多台主机,
另一个就是找到主机后如何可靠高效地进行数据传输。
在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,
由IP地址可以唯一地确定Internet上的一台主机。TCP层则
提供面向应用的可靠(TCP)的
或非可靠(UDP)的数据传输机制,
这是网络编程的主要对象,
一般不需要关心IP 层是如何处理数据的。
目前较为流行的网络编程模型是客户机/服务器(C/S)结构。
即通信双方,一方作为服务器
等待(另一方作为的)客户提出请求并予以响应。
客户则在需要服务时向服务器提出申请。
服务器一般作为守护进程始终运行,
监听网络端口,
一旦有客户请求,就会启动一个服务进程来响应该客户,
同时自己继续监听服务端口,
使后来的客户也能及时得到服务。
TCP/IP协议基础
TCP/IP是Transmission Control Protocol/Internet Protocol的简写,
中译名为传输控制协议/因特网协议,
又名网络通信协议,
是Internet最基本的协议、Internet国际互联网络的基础,
由网络层的IP协议和传输层的TCP协议组成。TCP/IP定义了电子设备如何连入因特网,
以及数据如何在它们之间传输的标准。TCP/IP协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
也就是说,TCP负责发现传输的问题,
一旦发现问题便发出信号要求重新传输,
直到所有数据安全正确地传输到目的地。
而IP的功能是给因特网的每一台电脑规定一个地址。TCP/IP协议不是TCP和IP这两个协议的合称,
而是指因特网整个TCP/IP协议簇。
从协议分层模型方面来讲,TCP/IP由4个层次组成,
分别是网络接口层、网络层、传输层、应用层。其实
TCP/IP协议并不完全符合OSI(Open System Interconnect)的7层参考模型,OSI是传统的开放式系统互连参考模型,
是一种通信协议的7层抽象的参考模型,
其中每一层执行某一特定任务。
该模型的目的是
使各种硬件在相同的层次上相互通信。
这7层是物理层、数据链路层(网络接口层)、网络层(网络层)、传送层(传输层)、会话层、表示层和应用层(应用层)。
而TCP/IP协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
由于ARPANET的设计者注重的是网络互联,
允许通信子网(网络接口层)采用已有的或是将来有的各种协议,
所以这个层次中没有提供专门的协议。
实际上,TCP/IP协议可以通过网络接口层连接到任何网络上,
例如X.25交换网或IEEE802局域网。
UDP协议
UDP是User Datagram Protocol的简称,
是一种无连接的协议,
每个数据报都是一个独立的信息,
包括完整的源地址或目的地址,
它在网络上以任何可能的路径传往目的地,
因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
在现实网络数据传输过程中,大多数功能是由TCP协议和UDP协议实现。(1)TCP协议
面向连接的协议,
在Socket之间进行数据传输之前必然要建立连接,
所以在TCP中需要连接时间。
TCP传输数据大小限制,
一旦连接建立起来,
双方的Socket就可以按统一的格式传输大的数据。
TCP是一个可靠的协议,
它确保接收方完全正确地获取发送方所发送的全部数据。(2)UDP协议
每个数据报中都给出了完整的地址信息,
因此无需要建立发送方和接收方的连接。
UDP传输数据时是有大小限制的,
每个被传输的数据报必须限定在64KB之内。
UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。
TCP、UDP选择的决定因素
(1)TCP在
网络通信上有极强的生命力,
例如远程连接(Telnet)和文件传输(FTP)
都需要不定长度的数据被可靠地传输。
但是可靠的传输是要付出代价的,
对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,
因此TCP传输的效率不如UDP高。(2)UDP
操作简单,而且仅需要较少的监护,
因此通常用于局域网高可靠性的分散系统中Client/Server 应用程序。
例如视频会议系统,
并不要求音频视频数据绝对的正确,
只要保证连贯性就可以了,
这种情况下显然使用UDP会更合理一些,
因为TCP和UDP都能达到这个保证连贯性的门槛,
但是TCP却要多占用更多的计算机资源,杀鸡焉用牛刀呢,
所有这种情况不用TCP,用UDP。
基于Socket的Java网络编程
网络上的两个程序通过一个
双向的通信连接实现数据的交换,
这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程方式,一个Socket由一个IP地址和一个端口号唯一确定。
但是,Socket所支持的协议种类也不光TCP/IP一种,
因此两者之间是没有必然联系的。
在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
1.Socket通信的过程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接收)消息,
一个连接就建立起来了。Server端和Client端都可以通过Send、Write等方法与对方通信。在
Java网络编程应用中,
对于一个功能齐全的Socket来说,
其工作过程包含如下所示的基本步骤。
(1)创建ServerSocket和Socket;
(2)打开连接到Socket的输入/输出流;
(3)按照一定的协议对Socket进行读/写操作;
(4)关闭IO流和Socket。
2.创建Socket
在
Java网络编程应用中,
包java.net中提供了两个类Socket和ServerSocket,
分别用来表示双向连接的客户端和服务端。
这是两个封装得非常好的类,
其中包含了如下所示的构造方法:Socket(InetAddress address, int port);Socket(InetAddress address, int port, boolean stream);Socket(String host, int prot);Socket(String host, int prot, boolean stream);Socket(SocketImpl impl);Socket(String host, int port, InetAddress localAddr, int localPort);Socket(InetAddress address, int port, InetAddress localAddr, int localPort);ServerSocket(int port);ServerSocket(int port, int backlog);ServerSocket(int port, int backlog, InetAddress bindAddr)在上述
构造方法中,
参数address、host和port分别是双向连接中另一方的IP地址、主机名和端口号,stream指明Socket是流Socket还是数据报Socket,localPort表示本地主机的端口号,localAddr和bindAddr是本地机器的地址(ServerSocket的主机地址),impl是Socket的父类,
既可以用来创建ServerSocket又可以用来创建Socket。
例如:
Socket client = new Socket("127.0.0.1", 80);
ServerSocket server = new ServerSocket(80);
- 注意:
必须小心地选择端口,每一个端口提供一种特定的服务,
只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,
例如HTTP服务的端口号为80,Telnet服务的端口号为21,FTP服务的端口号为23,
所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
另外,
在创建Socket时如果发生错误,将产生IOException,
在程序中必须对之做出处理。
所以在创建Socket或ServerSocket时必须捕获或抛出异常。
TCP编程详解
TCP/IP通信协议是一种可靠的网络协议,
能够在通信的两端各建立一个Socket,
从而在通信的两端之间形成网络虚拟链路。
一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。
Java语言对TCP网络通信提供了良好的封装,
通过Socket对象代表两端的通信端口,
并通过Socket产生的IO流进行网络通信。
这里先笔记Java应用中TCP编程的基本知识,
为后面的Android编程打下基础。
使用ServerSocket
在Java程序中,
使用
类ServerSocket接受其他通信实体的连接请求。对象ServerSocket的功能是监听来自客户端的Socket连接,
如果没有连接则会一直处于等待状态。在类
ServerSocket中包含了如下监听客户端连接请求的方法:Socket accept():如果接收到一个客户端Socket的连接请求,
该方法将返回一个与客户端Socket对应的Socket,
否则该方法将一直处于等待状态,线程也被阻塞。-
为了创建
ServerSocket对象,ServerSocket类为我们提供了如下构造器:ServerSocket(int port):
用指定的端口port创建一个ServerSocket,
该端口应该是有一个有效的端口整数值0~65535。ServerSocket(int port,int backlog):
增加一个用来改变连接队列长度的参数backlog。ServerSocket(int port,int backlog,InetAddress localAddr):
在机器(服务器、本机等)存在多个IP地址的情况下,
允许通过localAddr这个参数
来指定将ServerSocket绑定到指定的IP地址。
当使用
ServerSocket后,
需要使用ServerSocket中的方法close()关闭该ServerSocket。在通常情况下,
因为服务器不会只接受一个客户端请求,
而是会不断地接受来自客户端的所有请求,
所以可以通过循环来不断地调用ServerSocket中的方法accept()。例如下面的代码。
//创建一个ServerSocket,用于监听客户端Socket的连接请求
ServerSocket ss = new ServerSocket(30000);
//采用循环不断接受来自客户端的请求
while (true)
{
//每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
Socket s = ss.accept();
//下面就可以使用Socket进行通信了
...
}
- 在上述代码中,
创建的ServerSocket没有指定IP地址,
该ServerSocket会绑定到本机默认的IP地址。
在代码中使用30000作为该ServerSocket的端口号,
通常推荐使用10000以上的端口,
主要是为了避免与其他应用程序的通用端口冲突。
使用Socket
-
在客户端可以使用
Socket的构造器实现``和指定服务器的连接,
在Socket中可以使用如下两个构造器:Socket(InetAddress/String remoteAddress, int port):
创建连接到指定远程主机、远程端口的Socket,
该构造器没有指定本地地址、本地端口,本地IP地址和端口使用默认值。Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort):
创建连接到指定远程主机、远程端口的Socket,
并指定本地IP地址和本地端口号,
适用于本地主机有多个IP地址的情形。
在使用上述
构造器指定远程主机时,
既可使用InetAddress来指定,也可以使用String对象指定,
在Java中通常使用String对象指定远程IP,例如192.168.2.23。
当本地主机只有一个IP地址时,建议使用第一个方法,简单方便。
例如下面的代码:
//创建连接到本机、30000端口的Socket
Socket s = new Socket("127.0.0.1" , 30000);
当程序执行
上述代码后会连接到指定服务器,
让服务器端的ServerSocket的方法accept()向下执行,
于是服务器端和客户端就产生一对互相连接的Socket。
上述代码连接到“远程主机”的IP地址是127.0.0.1,
此IP地址总是代表本机的IP地址。
这里例程的服务器端、客户端都是在本机运行,
所以Socket连接到远程主机的IP地址使用127.0.0.1。当
客户端、服务器端产生对应的Socket之后,
程序无须再区分服务器端和客户端,
而是通过各自的Socket进行通信。-
在
Socket中提供如下两个方法获取输入流和输出流:InputStream getInputStream():
返回该Socket对象对应的输入流,
让程序通过该输入流从Socket中取出数据。OutputStream getOutputStream():
返回该Socket对象对应的输出流,
让程序通过该输出流向Socket中输出数据。
TCP协议的服务器端例程:
public class Server
{
public static void main(String[] args)
throws IOException
{
//创建一个ServerSocket,用于监听客户端Socket的连接请求
ServerSocket myss = new ServerSocket(30001);
//采用循环不断接受来自客户端的请求
while (true)
{
//每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
Socket s = myss.accept();
//将Socket对应的输出流包装成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
//进行普通IO操作
ps.println("凌川江雪!");
ps.println("望川霄云!");
ps.println("万年太久,只争朝夕!");
ps.println("人间正道是沧桑!");
ps.println("穷善其身,达济天下!");
//关闭输出流,关闭Socket
ps.close();
s.close();
}
}
}
- 上述代码建立了
ServerSocket监听,
并且使用Socket获取了输出流,
执行后不会显示任何信息。 - 对应的TCP协议的客户端例程:
public class Client {
public static void main(String[] args) throws IOException
{
Socket socket = new Socket("127.0.0.1" , 30001);
//将Socket对应的输入流包装成BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
//进行普通IO操作
StringBuilder response = new StringBuilder();
String line;
//一行一行地读取并加进stringbuilder
while((line = br.readLine()) != null){
response.append(line + "\n");
}
System.out.println("来自服务器的数据:" + "\n" + response.toString());
//关闭输入流、Socket
br.close();
socket.close();
}
}
-
上述代码使用Socket建立了与指定IP、指定端口的连接,
并使用Socket获取输入流读取数据,
之后处理一下数据然后打印在工作台。先运行服务端Class,再运行客户端Class,运行结果:
由此可见,
一旦使用ServerSocket和Socket建立网络连接之后,
程序通过网络通信与普通IO并没有太大的区别。
如果先运行上面程序中的Server类,
将看到服务器一直处于等待状态,
因为服务器使用了死循环来接受来自客户端的请求;
再运行Client类,
将可看到程序输出“来自服务器的数据:...!”,
这表明客户端和服务器端通信成功。
TCP中的多线程
刚刚实操的例程中,
Server和Client只是进行了简单的通信操作,
当服务器接收到客户端连接之后,服务器向客户端输出一个字符串,
而客户端也只是读取服务器的字符串后就退出了。在实际应用中,
客户端可能需要和服务器端保持长时间通信,
即服务器需要不断地读取客户端数据,
并向客户端写入数据,客户端也需要不断地读取服务器数据,
并向服务器写入数据。当使用
readLine()方法读取数据时,
如果在该方法成功返回之前线程被阻塞,则程序无法继续执行。
所以服务器很有必要为每个Socket单独启动一条线程,每条线程负责与一个客户端进行通信。另外,
因为客户端读取服务器数据的线程同样会被阻塞,
所以系统应该单独启动一条线程,该组线程专门负责读取服务器数据。假设要开发一个
聊天室程序,
在服务器端应该包含多条线程,
其中每个Socket对应一条线程,
该线程负责读取 Socket 对应输入流的数据
(从客户端发送过来的数据),
并将读到的数据
向每个Socket输出流发送一遍
(将一个客户端发送的数据“广播”给其他客户端);因此需要在
服务器端使用List来保存所有的Socket。
在具体实现时,
为服务器提供了如下两个类:
创建ServerSocket监听的主类。
处理每个Socket通信的线程类。
1/4 接下来介绍具体实现流程,首先看下面的IServerClass:
public class IServer
{
//定义保存所有Socket的ArrayList
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args)
throws IOException
{
ServerSocket ss = new ServerSocket(30000);
while(true)
{
//此行代码会阻塞,将一直等待别人的连接
Socket s = ss.accept();
socketList.add(s);
//每当客户端连接后启动一条ServerThread线程为该客户端服务
new Thread(new Serverxian(s)).start();
}
}
}
IServer类中,服务器端(ServerSocket )只负责接受客户端Socket的连接请求,
每当客户端Socket连接到该ServerSocket之后,
程序将客户端对应的Socket(客户Socket的对面一端)加入socketList集合中保存,
并为该Socket启动一条线程(Serverxian),
该线程负责处理 该Socket所有 的 通信任务。
小结:IServer类完成的业务是:
1.接收客户端Socket,
2.保存对应返回的Socket,
3.启动处理线程。
2/4 接着看服务器端线程类文件:
package liao.server;
import java.io.*;
import java.net.*;
import java.util.*;
//负责处理每个线程通信的线程类
public class Serverxian implements Runnable
{
//定义当前线程所处理的Socket
Socket s = null;
//该线程所处理的Socket所对应的输入流读取器
BufferedReader br = null;
public Serverxian(Socket s)
throws IOException
{
this.s = s;
//初始化该Socket对应的输入流
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
}
public void run()
{
try
{
String content = null;
//采用循环不断从Socket中读取客户端发送过来的数据
while ((content = readFromClient()) != null)
{
//遍历socketList中的每个Socket,
//将读到的内容向每个Socket发送一次
for (Socket s : IServer.socketList)
{
//将Socket对应的输出流包装成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println(content);
}
}
}
catch (IOException e)
{
//e.printStackTrace();
}
}
//定义读取客户端数据的方法
private String readFromClient()
{
try
{
return br.readLine();
}
//如果捕捉到异常,表明该Socket对应的客户端已经关闭
catch (IOException e)
{
//删除该Socket。
IServer.socketList.remove(s);
}
return null;
}
}
Serverxian类(服务器端线程类)中,
注意是线程类,继承Runnable,重写run方法
会不断读取客户端数据,
在获取时使用方法readFromClient()来读取客户端数据。
如果读取数据过程中捕获到 IOException异常,
则说明此Socket对应的客户端Socket出现了问题,
程序就会将此Socket从socketList中删除。
当服务器线程读到客户端数据之后会遍历整个socketList集合,
并将该数据向socketList集合中的每个Socket发送一次,
该服务器线程将把从Socket中读到的数据
向socketList中的每个Socket转发一次。
上述代码能够不断获取
Socket输入流中的内容,
当获取Socket输入流中的内容后,
直接将这些内容打印在控制台。
先运行上面程序中的类IServer,
该类运行后作为本应用的服务器,不会看到任何输出。接着可以运行多个 IClient——相当于启动多个聊天室客户端登录该服务器,此时在任何一个客户端通过键盘输入一些内容后单击“回车”键,将可看到所有客户端(包括自己)都会在控制台收到刚刚输入的内容,这就简单实现了一个聊天室的功能。-
运行结果如下动图所示:
(这个链接是
在Eclipse上,同时运行多个java程序,
用不同的console显示运行信息的方法)图中展现的是:已经启动服务端,
同时启动两个客户端,
来回切换客户端进行“聊天”,
客户端由于服务端的socket传输,
可以相互收到彼此的信息;
本文深入讲解Java中的Socket网络编程,涵盖TCP/IP协议基础、Socket通信原理及Java中的Socket类使用,包括ServerSocket和Socket的创建、连接过程及数据传输。通过实例演示了基于TCP的服务器与客户端通信,涉及多线程处理,实现聊天室功能。
1万+

被折叠的 条评论
为什么被折叠?



