1.1 创建TCP客户端
创建TCP客户端的基本步骤为:
String host = InetAddress.getLocalHost();
int port = 10002;
Socket client = new Socket(host,port);
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
client.close();
上面是构造一个TCP客户端的基本方法,我们来看一下各个步骤:
1.1.1 构造Socket对象
所以调用Socket的无参构造方法创建Socket对象时,我们需要利用connect()方法来和服务端对象进行连接,同时可以设置了等待连接的时间,一旦超过了这个时间,就会抛出SocketTimeOutException。
创建Socket客户端时可能抛出的异常:
- 抛出UnknownHostException的情况
如果无法识别服务端的主机或者客户端的主机的ip地址,那么就会抛出UnknownHostException。 - 抛出ConnectionException的情况
以下2中情况会抛出ConnectionException: 1、没有服务器进程监听指定的端口。 2、服务器进程拒绝连接。例如我们的服务端程序为ServerSocket(int port,int backlog)构造方法的第二个参数backlog,设定服务器的连接请求队列的长度,如果队列中的连接请求已满,服务器就会拒绝其余的连接请求。 - 抛出SocketTimeoutException的情况
当我们调用的时Socket的无参构造方法构建Socket对象的时候,那么这时候我们需要利用connect方法来设置连接的服务端的地址,同时需要设置等待连接的时间,一旦等待超时,那么就会抛出SocketTimeoutException。但是如果我们将等待的时间设置为0,表示永远不会超时。 - 抛出BindException的情况
如果客户端Socket对象无法和当前客户端对象指定的的ip地址和端口绑定,那么就会抛出BindException。
1.1.2 获取流信息
调用getInputStream()获取读取流,从而可以读取来自服务端的信息;调用getOutputStream()可以获取写入流,可以向服务端发送数据。 但是通常我们可以利用相应的流进行修饰。
BufferedReader bufR = new BufferedRead(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream(),true);
1.1.3 断开连接,释放资源
所以整个创建TCP客户端的代码格式应该是:
Socket client = null;
try{
client = new Socket(host,port);
BufferedReader bufR = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream(),true);
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(socket != null){
socket.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
1.2 获取Socket对象的相关信息
1.3 半关闭的Socket
如果进程A和进程B进行Socket通信,假定进程A输出数据,进程B读入数据。进程A如何告诉进程B所有的数据已经全部输出呢?主要由以下几种方法:
-
彼此约定一个结束标志,当进程A输出了这个结束表示,那么就将其发送给进程B,然后进程A结束,当进程B读取到这个结束标志之后,就可以知道进程A输出完毕了,不需要再进行读取的操作了。 -
进程A先发送一个信息,告诉进程B所发送的正文的长度,然后再发送正文。进程B先获取到了进程A发送的正文的长度,接下来只要读取完该长度的字符或者字节,就停止读取数据。 -
调用shutdownOutput()或者shutdownInput()方法,提醒对方,停止了发送数据或者停止读取数据。所以当进程A输出完毕之后,调用shutdownInput方法,那么进程B就可以知道已经读取完毕了。 -
当进程A发送所有的数据之后,调用close方法关闭进程A,那么当进程B读取了进程A发送的所有数据之后,再次读取的时候,例如调用的时read()返回的是-1,或者调用readLine()方法进行读取的时候返回的是null,就是因为进程A关闭了,此时的进程B不可以再读取了。 ???当客户端和服务端进行通信的时候,如果有一方突然结束程序,或者关闭了Socket,或者单独关闭了输入流或输出流,对另一方会造成什么影响? -
突然有一方结束程序 ???如果我们突然终止了TCP客户端(即客户端没有来得及调用close()方法,客户端程序结束了),那么就会抛出错误:Exception in thread "main" java.net.SocketException: Connection reset
TCP的客户端:
public class ClientDemo2 {
public static void main(String[] args) throws IOException {
Socket client = new Socket(InetAddress.getLocalHost(), 10002);
BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(client.getOutputStream(), true);
String line;
while((line = in.readLine()) != null){
out.println(line);
if(line.equals("over")){
break;
}
}
}
}
TCP服务端:
public class ServerDemo2 {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(10002);
Socket client = server.accept();
BufferedReader r
= new BufferedReader(new InputStreamReader(client.getInputStream()));
String line;
while((line = r.readLine()) != null){
System.out.println(line);
}
client.close();
server.close();
}
}
上面的代码中客户端还没有调用close方法就关闭程序了,那么这时候就会导致服务端再收到了客户端发送的over字样之后,将其打印到控制台中,然后就会发生了报错,提示Exception in thread "main" java.net.SocketException: Connection reset
所以我们将close()方法放在finally中,可以在一定的程度避免发生这样的错误。
???但是如果我们突然结束了服务端的程序,即没有调用close()方法关闭服务端,那么客户端并不会抛出错误,此时上面的代码依旧需要我们输入over字样的时候,客户端才会结束程序(这个可以我们再运行的时候,自己点击对应的地方来结束服务端的程序,之后发现客户端还在处于运行的状态,之后输入over,那么客户端就会结束程序了,证明结论是正确的)。
- 关闭了Socket
如果我们关闭了Socket对象,那么服务端再次读取数据的时候,就会默认为读取不到数据了,那么这时候如果我们利用的是InputStream调用的read方法,那么返回的是-1,如果利用的是BufferedRead的readLine()方法,那么返回的是null,此时服务端就会结束读取数据了。 - 单独关闭了输入流或者输出流
如果我们客户端单独关闭了输入流或者输出流,那么这时候只是提醒服务端,客户端已经停止读取数据了或者停止发送数据(即将所有的数据都发送出去了),那么这时候执行这一步之后,服务端就知道已经读取了全部数据了,那么不会再进行读取的操作。
1.3 创建ServerSocket服务端
再了解上面TCP的客户端Socket对象的相关操作之后,那么创建ServerSocket服务端的用法是相似的:
int port = 10002;
ServerSocket server = new ServerSocket(port);
Socket client = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream(),true);
client.close();
server.close();
如果我们需要实现当前的服务端需要和多个客户端创建连接,那么我们可以保证这个服务端不要关闭,一直都在运行的状态:
while(true){
Socket client = null;
try{
client = server.accept();
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(client != null){
client.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
其中创建ServerSocket对象有几种重载方式: 如果利用的是ServerSocket的无参构造方法进行构造ServerSocket对象的时候,那么这时候的ServerSocket对象并没有和任何端口进行绑定,需要我们执行bind()方法绑定端口通过这个无参构造方法,那么就可以允许服务器再绑定到指定的端口之前,先设置ServerSocket的一些选项。因为一旦服务器与特定的端口绑定之后,有一些选项不能再改变了,此时再取设置某一些选项,例如调用setReuseAddress(true)的时候不会在生效。
同时,在创建ServerSocket对象的时候,我们也可以设置这个服务端的连接请求队列backlog的大小,因为这个的存在,那么服务器可以同时监听到多个客户端的连接请求。
1.4 创建多线程的服务器
尽管服务器可以和多个客户端建立连接,但是服务端只能处理完一个客户端之后,才可以处理下一格客户端的请求。也就是说,多个客户必须要排队等候服务端的相应,只有处理完一个客户端之后,才可以处理下一个,不可以同时处理多个客户。
所以我们给每一个客户端分配一个线程的服务端代码应该是这样的:
public class ThreadServerDemo {
private ServerSocket server;
public ThreadServerDemo(int port) throws IOException {
server = new ServerSocket(port);
}
public static void main(String[] args) throws IOException {
ThreadServerDemo test = new ThreadServerDemo(10002);
test.service();
}
private void service() {
Socket client = null;
while(true){
try {
client = server.accept();
System.out.println("这是端口号为 " + client.getPort() + " 的客户和服务端建立了连接!");
Thread thread = new Thread(new SocketRunnable(client));
thread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
实现Runnable接口的子类为SocketRunnable:
public class SocketRunnable implements Runnable {
private Socket client;
public SocketRunnable(Socket client){
this.client = client;
}
@Override
public void run() {
try{
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream(), true);
String line;
while((line = in.readLine()) != null){
System.out.println(line);
out.println(line.toUpperCase());
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(client != null){
client.close();
}
}catch(IOException e){
e.printStackTrace();;
}
}
}
}
但是为每一个客户端创建一个线程会有弊端: 线程池解决方案看的不是很懂,所以就暂时不写了,大家可以自行去学习,我弄懂了之后再去更新哈!
|