对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket。服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信。这样就有两个Socket了,客户端和服务端各一个。

对于Socket之间的通信其实很简单,服务端往Socket的输出流里面写东西,客户端就可以通过Socket的输入流读取对应的内容。Socket与Socket之间是双向连通的,所以客户端也可以往对应的Socket输出流里面写东西,然后服务端对应的Socket的输入流就可以读出对应的内容。

 

public class Server {
 public static void main(String[] args) {
  try {
   ServerSocket server = new ServerSocket(9900);
   // 阻塞式方法,尝试接收连接 调用native方法接收请求
   Socket socket = server.accept();
   
   //接收数据
   Reader reader = new InputStreamReader(socket.getInputStream());
   char[] clientstr = new char[64];
   int length = reader.read(clientstr);
   System.out.println(new String(clientstr, 0, length));
   
   //发送数据
   Writer writer = new OutputStreamWriter(socket.getOutputStream());
   writer.write("Return param");
   writer.flush();//注意刷新流缓冲 ,避免双向等待
   writer.close();
   reader.close();
   socket.close();
   server.close();
 
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 
 }
}

 
server.accept()方法执行时,挂起等待接收连接 并得到Socket,其中accept 中调用的socketAccept方法为native方法,通过内核得到连接信息
 

 
public class Client {
 public static void main(String[] args) {
 
  try {
   Socket client = new Socket("127.0.0.1", 9900);
   Writer writer = new OutputStreamWriter(client.getOutputStream());
   writer.write("Request param");
   writer.flush();// 刷新该流的缓冲
   
   //获取返回结果
   Reader reader = new InputStreamReader(client.getInputStream());
   char[] clientstr = new char[64];
   int length = reader.read(clientstr);
   System.out.println(new String(clientstr, 0, length));
   writer.close();
   reader.close();
   client.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 
 }
}
 
这里要flush一下,只有这样服务端才能收到客户端发送的数据,否则可能会引起两边无限的互相等待

多个客户端连接同一个服务端

像前面讲的两个例子都是服务端接收一个客户端的请求之后就结束了,不能再接收其他客户端的请求了,这往往是不能满足我们的要求的。通常我们会这样做:

 
public class Server {

   public static void main(String args[]) throws IOException {
//为了简单起见,所有的异常信息都往外抛
int port = 8899;
//定义一个ServerSocket监听在端口8899上
ServerSocket server = new ServerSocket(port);
while (true) {
//server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
Socket socket = server.accept();
//跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//读完后写一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.flush();
writer.close();
reader.close();
socket.close();
}
} }

在上面代码中我们用了一个死循环,在循环体里面ServerSocket调用其accept方法试图接收来自客户端的连接请求。当没有接收到请求的时候,程序会在这里阻塞直到接收到来自客户端的连接请求,之后会跟当前建立好连接的客户端进行通信,完了后会接着执行循环体再次尝试接收新的连接请求。这样我们的ServerSocket就能接收来自所有客户端的连接请求了,并且与它们进行通信了。这就实现了一个简单的一个服务端与多个客户端进行通信的模式。

上述例子中虽然实现了一个服务端跟多个客户端进行通信,但是还存在一个问题。在上述例子中,我们的服务端处理客户端的连接请求是同步进行的,每次接收到来自客户端的连接请求后,都要先跟当前的客户端通信完之后才能再处理下一个连接请求。这在并发比较多的情况下会严重影响程序的性能,为此,我们可以把它改为如下这种异步处理与客户端通信的方式:

 
public class Server {

   public static void main(String args[]) throws IOException {
//为了简单起见,所有的异常信息都往外抛
int port = 8899;
//定义一个ServerSocket监听在端口8899上
ServerSocket server = new ServerSocket(port);
while (true) {
//server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
Socket socket = server.accept();
//每接收到一个Socket就建立一个新的线程来处理它
new Thread(new Task(socket)).start();
}
} /**
* 用来处理Socket请求的
*/
static class Task implements Runnable { private Socket socket; public Task(Socket socket) {
this.socket = socket;
} public void run() { try { handleSocket();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 跟客户端Socket进行通信
* @throws Exception
*/
private void handleSocket() throws Exception {
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//读完后写一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.flush();
writer.close();
reader.close();
socket.close();
} } }

在上面代码中,每次ServerSocket接收到一个新的Socket连接请求后都会新起一个线程来跟当前Socket进行通信,这样就达到了异步处理与客户端Socket进行通信的情况。

在从Socket的InputStream中接收数据时,像上面那样一点点的读就太复杂了,有时候我们就会换成使用BufferedReader来一次读一行,如:

 
public class Server {

   public static void main(String args[]) throws IOException {
//为了简单起见,所有的异常信息都往外抛
int port = 8899;
//定义一个ServerSocket监听在端口8899上
ServerSocket server = new ServerSocket(port);
while (true) {
//server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
Socket socket = server.accept();
//每接收到一个Socket就建立一个新的线程来处理它
new Thread(new Task(socket)).start();
}
} /**
* 用来处理Socket请求的
*/
static class Task implements Runnable { private Socket socket; public Task(Socket socket) {
this.socket = socket;
} public void run() {
try {
handleSocket();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 跟客户端Socket进行通信
* @throws Exception
*/
private void handleSocket() throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((temp=br.readLine()) != null) {
System.out.println(temp);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//读完后写一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.write("eof\n");
writer.flush();
writer.close();
br.close();
socket.close();
}
}
}

这个时候需要注意的是,BufferedReader的readLine方法是一次读一行的,这个方法是阻塞的,直到它读到了一行数据为止程序才会继续往下执行,那么readLine什么时候才会读到一行呢?直到程序遇到了换行符或者是对应流的结束符readLine方法才会认为读到了一行,才会结束其阻塞,让程序继续往下执行。所以我们在使用BufferedReader的readLine读取数据的时候一定要记得在对应的输出流里面一定要写入换行符(流结束之后会自动标记为结束,readLine可以识别),写入换行符之后一定记得如果输出流不是马上关闭的情况下记得flush一下,这样数据才会真正的从缓冲区里面写入。对应上面的代码我们的客户端程序应该这样写:

 
public class Client {

   public static void main(String args[]) throws Exception {
//为了简单起见,所有的异常都直接往外抛
String host = "127.0.0.1"; //要连接的服务端IP地址
int port = 8899; //要连接的服务端对应的监听端口
//与服务端建立连接
Socket client = new Socket(host, port);
//建立连接后就可以往服务端写数据了
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server.");
writer.write("eof\n");
writer.flush();
//写完以后进行读操作
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
StringBuffer sb = new StringBuffer();
String temp;
int index;
while ((temp=br.readLine()) != null) {
if ((index = temp.indexOf("eof")) != -1) {
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from server: " + sb);
writer.close();
br.close();
client.close();
}
}

4、设置超时时间

假设有这样一种需求,我们的客户端需要通过Socket从服务端获取到XX信息,然后给用户展示在页面上。我们知道Socket在读数据的时候是阻塞式的,如果没有读到数据程序会一直阻塞在那里。在同步请求的时候我们肯定是不能允许这样的情况发生的,这就需要我们在请求达到一定的时间后控制阻塞的中断,让程序得以继续运行。Socket为我们提供了一个setSoTimeout()方法来设置接收数据的超时时间,单位是毫秒。当设置的超时时间大于0,并且超过了这一时间Socket还没有接收到返回的数据的话,Socket就会抛出一个SocketTimeoutException。

假设我们需要控制我们的客户端在开始读取数据10秒后还没有读到数据就中断阻塞的话我们可以这样做:

public class Client {

   public static void main(String args[]) throws Exception {
//为了简单起见,所有的异常都直接往外抛
String host = "127.0.0.1"; //要连接的服务端IP地址
int port = 8899; //要连接的服务端对应的监听端口
//与服务端建立连接
Socket client = new Socket(host, port);
//建立连接后就可以往服务端写数据了
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server.");
writer.write("eof\n");
writer.flush();
//写完以后进行读操作
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
//设置超时间为10秒
client.setSoTimeout(10*1000);
StringBuffer sb = new StringBuffer();
String temp;
int index;
try {
while ((temp=br.readLine()) != null) {
if ((index = temp.indexOf("eof")) != -1) {
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
} catch (SocketTimeoutException e) {
System.out.println("数据读取超时。");
}
System.out.println("from server: " + sb);
writer.close();
br.close();
client.close();
}
}

5、接收数据乱码

对于这种服务端或客户端接收中文乱码的情况通常是因为数据发送时使用的编码跟接收时候使用的编码不一致。比如有下面这样一段服务端代码:

 
public class Server {

   public static void main(String args[]) throws IOException {
//为了简单起见,所有的异常信息都往外抛
int port = 8899;
//定义一个ServerSocket监听在端口8899上
ServerSocket server = new ServerSocket(port);
while (true) {
//server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
Socket socket = server.accept();
//每接收到一个Socket就建立一个新的线程来处理它
new Thread(new Task(socket)).start();
}
} /**
* 用来处理Socket请求的
*/
static class Task implements Runnable { private Socket socket; public Task(Socket socket) {
this.socket = socket;
} public void run() {
try {
handleSocket();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 跟客户端Socket进行通信
* @throws Exception
*/
private void handleSocket() throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((temp=br.readLine()) != null) {
System.out.println(temp);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("客户端: " + sb);
//读完后写一句
Writer writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
writer.write("你好,客户端。");
writer.write("eof\n");
writer.flush();
writer.close();
br.close();
socket.close();
}
}
}

这里用来测试我就弄的混乱了一点。在上面服务端代码中我们在定义输入流的时候明确定义了使用GBK编码来读取数据,而在定义输出流的时候明确指定了将使用UTF-8编码来发送数据。如果客户端上送数据的时候不以GBK编码来发送的话服务端接收的数据就很有可能会乱码;同样如果客户端接收数据的时候不以服务端发送数据的编码,即UTF-8编码来接收数据的话也极有可能会出现数据乱码的情况。所以,对于上述服务端代码,为使我们的程序能够读取对方发送过来的数据,而不出现乱码情况,我们的客户端应该是这样的:

public class Client {
 
   public static void main(String args[]) throws Exception {
      //为了简单起见,所有的异常都直接往外抛
     String host = "127.0.0.1";  //要连接的服务端IP地址
     int port = 8899;   //要连接的服务端对应的监听端口
     //与服务端建立连接
     Socket client = new Socket(host, port);
      //建立连接后就可以往服务端写数据了
     Writer writer = new OutputStreamWriter(client.getOutputStream(), "GBK");
      writer.write("你好,服务端。");
      writer.write("eof\n");
      writer.flush();
      //写完以后进行读操作
     BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
      //设置超时间为10秒
     client.setSoTimeout(10*1000);
      StringBuffer sb = new StringBuffer();
      String temp;
      int index;
      try {
         while ((temp=br.readLine()) != null) {
            if ((index = temp.indexOf("eof")) != -1) {
                sb.append(temp.substring(0, index));
                break;
            }
            sb.append(temp);
         }
      } catch (SocketTimeoutException e) {
         System.out.println("数据读取超时。");
      }
      System.out.println("服务端: " + sb);
      writer.close();
      br.close();
      client.close();
   }
}

【JAVA】Socket 编程的更多相关文章

  1. Java Socket编程题库

    一.    填空题 ___ IP地址____用来标志网络中的一个通信实体的地址.通信实体可以是计算机,路由器等. 统一资源定位符URL是指向互联网"资源"的指针,由4部分组成:协议 ...

  2. Java Socket编程(转)

    Java Socket编程 对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首 ...

  3. Java基础:三步学会Java Socket编程

    Java基础:三步学会Java Socket编程 http://tech.163.com 2006-04-10 09:17:18 来源: java-cn 网友评论11 条 论坛        第一步 ...

  4. 如何为可扩展系统进行Java Socket编程

    从简单I/O到异步非阻塞channel的Java Socket模型演变之旅 上世纪九十年代后期,我在一家在线视频游戏工资工作,在哪里我主要的工作就是编写Unix Unix Berkley Socket ...

  5. 多线程Java Socket编程示例

    package org.merit.test.socket; import java.io.BufferedReader; import java.io.IOException; import jav ...

  6. 转:Java Socket编程

    对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首先ServerSocket将 ...

  7. Java Socket编程基础篇

    原文地址:Java Socket编程----通信是这样炼成的 Java最初是作为网络编程语言出现的,其对网络提供了高度的支持,使得客户端和服务器的沟通变成了现实,而在网络编程中,使用最多的就是Sock ...

  8. Java Socket编程详细解说

    Java Socket编程 JavaSocketServerSocket乱码超时 Java Socket编程 对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是So ...

  9. Java Socket编程如何建立两者关系

    转自:http://developer.51cto.com/art/201003/190582.htm Java Socket编程需要大家详细的学习,有关的技术一直在不断的更新.那么要如何才能掌握好有 ...

  10. 20182332 实验四《Java Socket编程 》实验报告

    20182332 实验肆<数据结构与面向对象程序设计>实验报告 课程:<程序设计与数据结构> 班级: 1823 姓名: 盛国榕 学号:20182332 实验教师:王志强 实验日 ...

随机推荐

  1. python之路二十一

    URL        - 两个    Views        - 请求的其他信息        from django.core.handlers.wsgi import WSGIRequest   ...

  2. 整理一下Entity Framework的查询 [转]

    Entity Framework是个好东西,虽然没有Hibernate功能强大,但使用更简便.今天整理一下常见SQL如何用EF来表达,Func形式和Linq形式都会列出来(本人更喜欢Func形式). ...

  3. Docker - command in docker container

    1.查看Container 里面运行的进程 在运行容器以后,可以查看里面的进程: docker top <container_id> or <container_name> 2 ...

  4. monkeyrunner脚本录制

    1.在窗口输入 monkeyrunner monkey_recorder.py  调用录制脚本工具 2.在窗口输入 monkeyrunner monkey_playback.py  d:\game   ...

  5. WPF去边框与webbrowser的冲突

    首先建一个类,比如NativeMethods.cs class NativeMethods{     public const int WS_CAPTION=0x00C0000;     public ...

  6. MySQL延迟复制--percona-toolkit和MASTER TO MASTER_DELAY

    为了数据的安全,有的时候数据库需要延迟备份,这里说下两种延迟备份的方法. 一.借助工具. 实现环境: 192.168.189.143 (mysql主库) 192.168.189.144 (mysql备 ...

  7. 泛型T的类型获取

    T.getClass()或者T.class都是非法的,因为T是泛型变量. 由于一个类的类型是什么是在编译期处理的,故不能在运行时直接在Base里得到T的实际类型. /** * 可以在service层直 ...

  8. mysql主从

    1       系统规划 1.1     mysql版本 Mysql版本 mysql-5.5.51 1.2     服务器地址 服务器地址 10.180.2.167 1.3     mysql目录 主 ...

  9. 关于StringBuffer和StringBuilder

    StringBuffer 字符串特点:字符串是常量:它们的值在创建之后不能更改. 字符串的内容一旦发生了变化,那么马上会创建一个新 的对象. 注意: 字符串的内容不适宜频繁修改,因为一旦修改马上就会创 ...

  10. mvc file控件无刷新异步上传操作

    前言 上传文件应该是很常见必不可少的一个操作,网上也有很多提供的上传控件.今天遇到一个问题:input控件file无法进行异步无刷新上传.真真的感到别扭.所以就尝试这去处理了一下.主要分三个部分:上传 ...