TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket ,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。 JAVA对基于TCP/IP协议的网络通信提供了良好的封装。java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信.
(一)阻塞式的Scoket通信
(1)使用ServletSocket创建TCP服务器
TCP通信的两个实体之间并没有服务器,客户端之分,但那是两个通信实体已经建立虚拟链路之后。。在两个通信实体没有建立虚拟链路之前,必须有一个通信实体先做出“主动姿态”,主动接收来自其它通信实体的连接请求.
Java中能接收其它通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。
Socket accept():如果接收到一个客户端的 Socket连接请求,该方法将返回一个与客户端Socket对应的Socket,否则该方法一直处于等待状态,线程也被阻塞。
为了创建ServerSocket对象,ServerSocket类提供了如下几个构造器
ServerSocket(int port)
ServerSocket(int port,int backlog)
ServerSocket(int port,int backlog,InetAddress localAddr)
当ServerSocket使用完毕,应使用ServerSocket的close()方法来关闭该ServerSocket。通常情况下,服务器不应该只接爱一个客户端请求,而应该不断地接受来自客户端的所有请求,所以java程序通常会通过循环,不断地调用ServerSocket的accept()方法。
如下代码断
//创建一个ServerSocket,用于监听客户端Socket的连接请求
ServerSocket ss = new ServerSocket(30000);
//采用循环不断接受来自客户端的请求
while (true)
{
//每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
Socket s = ss.accept();
//将Socket对应的输出流包装成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
//进行普通IO操作
ps.println("欢迎您跟随阿堂(网络时空)一起来学习Scoket的相关知识");
//关闭输出流,关闭Socket
ps.close();
s.close();
}
(2)使用Socket进行通信
客户端通常使用Socket的构造器来连接到指定服务器,Socket通常可使用如下两个构造器
Socket(InetAddress/String remoteAddress,int port)
Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort)
如
Socket socket = new Socket("127.0.0.1" , 30000);
// 下面就可以使用Socket进行通信了
……
当程序执行上面代码时,该代码将连接到指定ip地址和端口的服务器,让服务器的ServerSocket的accept()方法向下执行,于是服务器和客户端就产生一对互相连接的Socket了
当客户端,服务器产生了对应的Socket之后,这时候程序就无须区分服务器,客户端了,而是通过各自的Socket进行通信,Socket提供了如下两个方法来获取输入流和输出流
InputStream getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据.
OutputStream getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据.
如:
import java.net.*;
import java.io.*;
public class Server
{
public static void main(String[] args)
throws IOException
{
//创建一个ServerSocket,用于监听客户端Socket的连接请求
ServerSocket ss = new ServerSocket(30000);
//采用循环不断接受来自客户端的请求
while (true)
{
//每当接受到客户端Socket的请求,服务器端也对应产生一个Socket
Socket s = ss.accept();
//将Socket对应的输出流包装成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
//进行普通IO操作
ps.println("服务器向你发送信息啦!");
//关闭输出流,关闭Socket
ps.close();
s.close();
}
}
}
(3)加入多线程
当我们使用传统的BufferReader的readLine()方法读取数据时,当该方法成功返回之前,线程被阻塞,程序无法继续执行。考虑到这个原因,因此服务器应该为每个Socket单独启动一条线程,每条线程与一个客户端进行通信.
同样,客户端读取服务器数据时,线程也会被阻塞,所以系统也应该为客户端单独启动一条线程,该客户端线程专门负责读取服务器数据.
下面的实现一个命令行的C/S聊天室应用,服务器应该包含多条线程,每个Socket对应一条线程,该线程负责读取Socket对应输入流的数据(从客户端发送过来的数据),并将读到的数据向每个 Socket输出流发送一遍(将每一个客户端的数据”广播”给其它客户端),因此需要在服务器端使用List来保存所有的Socket。
服务器端提供了两个类:一个是创建 ServerSocket监听的主类,一个是负责处理每个Scoket通信的线程类
如下所示
import java.net.*;
import java.io.*;
import java.util.*;
public class MyServer
{
//定义保存所有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 ServerThread(s)).start();
}
}
}
import java.io.*;
import java.net.*;
import java.util.*;
//负责处理每个线程通信的线程类
public class ServerThread implements Runnable
{
//定义当前线程所处理的Socket
Socket so = null;
//该线程所处理的Socket所对应的输入流
BufferedReader br = null;
public ServerThread(Socket s)
throws IOException
{
this.so = s;
//初始化该Socket对应的输入流
br = new BufferedReader(new InputStreamReader(so.getInputStream()));
}
public void run()
{
System.out.println("----Client :" + s.getPort() + " connected-----");
while (true) {
String line = null;
while ((line = readFromClient()) != null) {
for (Socket socket : MyTCPServer.socketList) {
if (socket == s) {
continue;
}
try {
System.out.println("send to socket :" + s.getPort());
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(line);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//定义读取客户端数据的方法
private String readFromClient()
{
try
{
return br.readLine();
}
//如果捕捉到异常,表明该Socket对应的客户端已经关闭
catch (IOException e)
{
//删除该Socket。
MyServer.socketList.remove(s);
}
return null;
}
}
客户端也同样提供了两个类:一个是负责读取键盘输入的Myclient,并将用户输入的数据写入Socket对应的输出流中,也就是客户端程序的主线程类,一个是负责读取Socket对应输入流中的数据(从服务器发送过来的数据).
import java.net.*;
import java.io.*;
public class MyClient
{
public static void main(String[] args)
throws IOException
{
Socket s = s = new Socket("127.0.0.1" , 30000);
//客户端启动ClientThread线程不断读取来自服务器的数据
new Thread(new ClientThread(s)).start();
//获取该Socket对应的输出流
PrintStream ps = new PrintStream(s.getOutputStream());
System.out.println("input:");
Scanner scanner = new Scanner(System.in);
while (true) {
// System.out.println(scanner.next());
ps.println(s.getLocalPort()+":"+scanner.next());
}
}
}
import java.io.*;
import java.net.*;
import java.util.*;
public class ClientThread implements Runnable
{
//该线程负责处理的Socket
private Socket s;
//该现成所处理的Socket所对应的输入流
BufferedReader br = null;
public ClientThread(Socket s)
throws IOException
{
this.s = s;
br = new BufferedReader(
new InputStreamReader(s.getInputStream()));
}
public void run()
{
System.out.println("----Client : "
+ s.getLocalPort() + " start-----");
try {
while (true) {
String line = null;
while ((line = br.readLine()) != null) {
String[] split = line.split(":");
String src = split[0];
String content = split[1];
System.out.println("receive from " + src + ":" + content);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}