第一篇讲述了什么是Socket和什么是java Socket。于是就可以进行实践了。
这个例子是一个经典的C/S模型。
首先,我们需要一个服务器。和一个客户端。
但是,前文讲了,socket链接是一个点对点的链接方式,并没有服务器客户端之说,产生socket链接的双方是对等的,都持有一个socket,用来建立流,产生数据交互。
如我们创建的阻塞式的传统IO流,那么一定会发生阻塞的情况。因为一个线程执行到read()方法时,就阻塞了。那么明显的,如果我们只用一个线程作为服务器的话,是无法应对多个客户端的链接需求的。
举个形象的例子,服务器就是一个人,客户端也是一个人。2个人通话的时候不允许干其他的事情。如果是这样的话,那么如果第一个用户一直霸占着客户端,那么下面的用户都是无法进行链接的。解决方案也很简单,现在派出一个人,作为客户分配者,类似于公司前台的存在,只负责分配任务。来一个客户,前台分配一个具体的负责人(也就是一个处理线程)给他。由这个线程与客户进行通讯,前台继续等待下一个客户的到来。
这个例子中,有几个对象。
前台:服务器的线程分配者 即 java.net.ServerSocket
客户:也就是客户端:Client 自己实现
具体的负责人:ServerClient 自己实现 。一个线程对象持有一个ServerSocket分配的Socket
流程如下:
服务器启动,然后阻塞,等待客户端的链接。
客户端新建一个Socket,传入地址和端口,进行链接。
ServerSocket接受此客户端,创建一个socket对象。
服务器将用此socket对象创建 线程对象ServerClient
然后服务器启动ServerClient对象,ServerSocket方法执行accpet()方法阻塞,等待下一个客户端的链接。
代码如下 客户端:
package xin.tomdonkey.demo01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Random;
public class Client
{
Socket socket ;
boolean flag = false;
InputStream inputStream ;
OutputStream outputStream ;
public Client(String address,int port) throws IOException
{
this.socket = new Socket(address,port);
this.inputStream = socket.getInputStream();
this.outputStream = socket.getOutputStream();
}
public void startup() throws IOException, InterruptedException
{
flag = true;
while (flag)
{
//由客户端发起,向服务器写出数据,一个随机byte值
byte b = (byte)new Random().nextInt(100);
outputStream.write(b);
System.out.println("写出:" + b);
//写出完成之后,线程阻塞,直到收到服务器发来的值为止
int r = inputStream.read();
System.out.println("收到:" + r);
System.out.println("完成一次请求\n");
//收到之后线程休眠2s,再向服务器写出数据
Thread.sleep(2000);
}
}
public static void main(String[] args) throws IOException, InterruptedException
{
Client client = new Client("127.0.0.1",6655);
client.startup();
}
}
服务器:一个线程分配器
package xin.tomdonkey.demo01;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server
{
int port = 6655;
boolean flag = false;
ServerSocket serverSocket ;
/**
* 采用默认端口6655创建服务器
* @throws IOException
*/
public Server() throws IOException
{
this.serverSocket = new ServerSocket(port);
}
/**
* 指定端口port创建服务器
* @param port 服务器监听的端口
* @throws IOException
*/
public Server(int port) throws IOException
{
this.port = port;
this.serverSocket = new ServerSocket(port);
}
/**
* 启动服务器,进行监听和分配线程操作。
* 此方法会将服务器的状态变更为启动,即flag = true
* @throws IOException
*/
public void startUp() throws IOException
{
flag = true;
while (flag)
{
Socket socket = serverSocket.accept();
Thread serverClientThread = new Thread(new ServerClient(socket));
serverClientThread.start();
}
}
public static void main(String[] args) throws IOException
{
new Server().startUp();
}
}
服务器线程对象:进行具体通讯的线程对象
package xin.tomdonkey.demo01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Random;
/**
* 用来处理每一个连接的线程类。
* 每一个客户端的链接都会由Server分配一个此类的线程对象进行处理。
*/
public class ServerClient implements Runnable
{
Socket socket ;
InputStream inputStream ;
OutputStream outputStream ;
/**
* 由Server创建,传入socket进行处理。
* @param socket
* @throws IOException
*/
public ServerClient(Socket socket) throws IOException
{
this.socket = socket;
this.inputStream = socket.getInputStream();
this.outputStream = socket.getOutputStream();
}
@Override
public void run()
{
while (true)
{
try
{
//线程启动,即阻塞,直到客户端发出数据
//可以做一个优化,收不到数据即销毁当前线程以及链接和流
int r= inputStream.read();
System.out.println("收到:" + r);
//收到后即回复给客户端一个随机值
byte b = (byte)new Random().nextInt(100);
outputStream.write(b);
System.out.println("写出:" + b);
System.out.println("完成一次回复\n");
}
catch (IOException e)
{
//客户端掉线了 抛出此异常
e.printStackTrace();
}
}
}
}
注:基础版本v1.0
[简介]
启动Server之后
启动Client
client会自动向Server发出一次请求,每隔2s请求一次,
此版本不讨论服务器异常问题,写入操作执行完成之后,即阻塞,等待回复
[待解决问题]
异常基本都为抛出操作
采用基础阻塞式io
不添加缓冲区操作
不添加销毁回收操作
每次传输都传输一个随机的byte值