Java中的Socket编程(3)-深入

[size=medium]
[b]读不在三更五鼓,功只怕一曝十寒。—郭沫若[/b]
意思是说,学习不在某一时的努力,而要锲而不舍的坚持才能成功!
这句话告诉我们,学习是一个循序渐进的积累过程,急于求成是不可取的,而想一劳永逸,想到的时候就用功一时,想不到的时候就疏于学业,这样也只能算是学无所成的。

上一次我们实现了一个服务端和客户端同时读写的程序,但是服务端在处理完一个客户端的请求之后就结束了,而不能继续接收其他客户端的请求,这种方式还不能满足我们实际开发中的需要。今天我们来实现一个更贴近实际情况的程序:

[b]1、需求:一个服务端异步处理多个客户端的请求[/b]
核心点:服务端通过accept方法监听客户端请求,当接收到某个客户端请求时,开启一个新的线程来处理请求,然后继续回到监听状态。这样就实现了异步处理客户端请求的程序。

[b]2、代码实现:[/b]
[b]1)服务端代码:[/b]
[/size]

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
* 服务端
* 需求:多个客户端连接同一个服务端
* @author Sam
*
*/
public class MultipleServer {

/** 编码 */
public static final String ENCODING = "UTF-8";

/**
* @param args
*/
public static void main(String[] args) {

try {
// 创建一个服务端ServerSocket,监听指定端口的请求
ServerSocket ss = new ServerSocket(10000);
System.out.println("Server 等待客户端接入...");
while (true) {
// 一直循环监听客户端请求
Socket socket = ss.accept();
// 开启一个新线程处理Socket请求
new Thread(new AsynSocketTask(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 用于处理Socket请求的任务类
*
*/
static class AsynSocketTask implements Runnable {

private Socket socket;
private BufferedReader buffReader;
private BufferedWriter buffWriter;

public AsynSocketTask(Socket socket) {
try {
this.socket = socket;
// 获取Socket中的输入输出流,并使用带缓冲区的字符流
buffReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), ENCODING));
buffWriter = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), ENCODING));
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void run() {
handleSocketRequest();
}

/**
* 处理客户端请求
*/
private void handleSocketRequest() {
try {
读操作 //
// 获取客户端数据
String line = null;
StringBuilder sBuilder = new StringBuilder();
// 一行一行的读
while ( (line=buffReader.readLine()) != null ) {
if (line.indexOf("eof") != -1) {// 读到结束标记,则跳出循环
break;
}
sBuilder.append(line);
}
System.out.println("Server 来自客户端的数据:"+ sBuilder.toString());

写操作 //
// 读完之后,往客户端写一句
buffWriter.write("Hello Client!");
buffWriter.newLine();// 写一个换行符
buffWriter.write("eof");// 写一个结束标记符
buffWriter.newLine();

buffWriter.close();// 关闭该流的同时,也会释放与之关联的所有资源
buffReader.close();
socket.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

}

[size=medium]
[b]服务端代码分析:[/b]
1、在主程序中,我们使用了一个while(true)死循环,监听客户端请求,当监听到有请求过来,就开启一个新线程来处理,然后主程序回到原来的监听状态。实际开发中也是这种情况的,服务端应该一直处于开启状态,可以随时响应客户端请求。
2、为了避免出现乱码,服务端和客户端读写数据统一使用UTF-8编码格式。
3、为了提高读写效率,程序中我们使用了带缓存区的字符输入输出流,一行一行的读写数据;其中[b]readLine方法是阻塞式[/b]操作的,只有读到一个换行、回车或换行回车符时才会执行往后的操作,否则一直处于阻塞状态。而[b]newLine方法[/b]是往流中写一个换行符。

[b]2)客户端代码:[/b]
[/size]

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
* 客户端
* @author Sam
*
*/
public class MultipleClient {

/** 编码 */
public static final String ENCODING = "UTF-8";

/**
* @param args
*/
public static void main(String[] args) {
try {
// 与服务端建立连接(服务端主机号,服务端监听的端口号)
Socket socket = new Socket("127.0.0.1" , 10000);
// 获取Socket中的输入输出流,并使用带缓冲区的字符流
BufferedWriter buffWriter = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), ENCODING));
BufferedReader buffReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), ENCODING));

/// 写操作 ///
// 往服务端写数据
buffWriter.write("Hello Server!");
buffWriter.newLine();// 写一个换行符
buffWriter.write("eof");// 写一个结束标记符
buffWriter.newLine();
buffWriter.flush();

/// 读操作 ///
// 写完之后,读取服务端返回的数据
socket.setSoTimeout(10*1000);// 设置超时时长10秒
String line = null;
StringBuilder sBuilder = new StringBuilder();
// 一行一行的读
while ( (line=buffReader.readLine()) != null ) {
if (line.indexOf("eof") != -1) {// 读到结束标记,则跳出循环
break;
}
sBuilder.append(line);
}

buffWriter.close();
buffReader.close();
socket.close();

System.out.println("Client 来自服务端的数据:" + sBuilder.toString());

} catch (IOException e) {
e.printStackTrace();
}
}

}

[size=medium]
客户端代码分析:
1、这里的代码逻辑,跟前两篇文章中的代码是一样的,只是有个需要注意的方法:[b]setSoTimeout(int timeout)[/b]
设置超时时长,单位是毫秒。当设置了超时时长大于0时,表示Socket在这一时间内,如果没读取到数据,则不会一直阻塞在那里,而是抛出一个SocketTimeoutException异常。

[b]3、运行结果:[/b]
注意:先运行Server程序,再运行Client程序。为体现服务端可以处理多个客户端请求,把Client程序拷贝到硬盘的某个路径,编辑文件,修改往服务端写数据的内容,然后在dos控制台再运行一次。即
[img]http://dl2.iteye.com/upload/attachment/0096/1670/0e22a9c0-96b2-313b-9dac-40fb8e156f6c.jpg[/img]

[b]结果如下: [/b]
[b]1)Server程序控制台:[/b]
[img]http://dl2.iteye.com/upload/attachment/0096/1662/5b652dad-af3c-3769-852d-8fe9553385d4.jpg[/img]

[b]2)Client程序控制台:[/b]
[img]http://dl2.iteye.com/upload/attachment/0096/1664/3dc6da69-0d60-3e15-9db6-3f700638920e.jpg[/img]

[b]3)dos命令控制台:[/b]
[img]http://dl2.iteye.com/upload/attachment/0096/1666/369783bc-c0a0-3d27-ba4d-828926890d86.jpg[/img]

[b]4、总结:[/b]
经过前面两章和本章的学习,我相信大家对Socket会有了一个更深入的了解,前面两章的内容均是为了本章内容而做铺垫的,实际开发中,就是使用本章这种方式,一个服务端异步响应多个客户端请求。以后更复杂的程序也是在这基础上拓展的,要学会融会贯通,以不变应万变。
[/size]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值