端口号可以从0~65535:
今天就写TCP相关。在下一节我会分别写有关UDP,还有MultiCastSocket。
Socket的工作原理:
通信两端都建立一个Socket,从而两端形成虚拟链路。通过IO流完成网络通信。
实现两台终端进行通信需使用IP地址与port。
InetAddress:可获取IP地址 & 主机名的类
实例:
import java.io.IOException; import java.net.InetAddress; public class InetAnddressTest { public static void main(String[] args) throws IOException { InetAddress ip = InetAddress.getByName("www.baidu.com"); System.out.println(ip.getHostAddress()); System.out.println(ip.getHostName()); System.out.println("isReachable:"+ip.isReachable(5000)); InetAddress ip2 = InetAddress.getLocalHost(); System.out.println(ip2.getHostAddress()); System.out.println(ip2.getHostName()); System.out.println("isReachable:"+ip2.isReachable(5000)); } }
先了解TCP:
当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体的确认信息(握手)。如果没有收到,则会再次发送刚才发送的信息。建立虚拟链路之前必须有一个主动接受来自其他通信实体的连接请求。即ServerSocket。
ServerSocket(上图右边的)【服务端】:
ServerSocket(int port):用端口来创建一个ServerSocket,此时localAddress为默认为服务ip。如果有终端有多个ip(多网卡)时可以用带ip参数的构造方法。
accept():等待接受Socket的连接请求,返回一个发送连接请求的客户端socket对象。
getInputStream()/ getOuptStream():获取输入输出流来完成读写操作。
Socket(上图左边的)【客户端】:
Socket(InetAddress/String remoteAddress,int port):指定服务器的ip地址,端口号。
*如果new了一个客户端Socket将会连接到服务端。即accept方法返回socket。
getInputStream()/ getOuptStream():获取输入输出流来完成读写操作。
SetTimeOut(int timeout):当超出限定时间会抛出SocketTimeoutException异常。
*s.connconnect(new InetAddress(host,port),1000):连接中可以设定连接超时。
实例:
服务端代码:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class Server { public static List<Socket> clients = new ArrayList<Socket>(); public static void main(String[] args) { int port = 1025; try { ServerSocket serverSocket = new ServerSocket(port); while (true) { Socket client = serverSocket.accept(); clients.add(client); new Thread(new Client(client)).start(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static class Client implements Runnable { Socket client; BufferedReader br; public Client(Socket client) throws IOException { this.client = client; br = new BufferedReader(new InputStreamReader( client.getInputStream())); } public String read() { try { return br.readLine(); } catch (IOException e) { // TODO Auto-generated catch block clients.remove(client); } return null; } public void run() { // TODO Auto-generated method stub try { String msg = null; while ((msg = read()) != null) { for (Socket s : clients) { PrintStream pw = new PrintStream(s.getOutputStream()); pw.println(msg); System.out.println(msg); } } br.close(); } catch (IOException e) { // TODO Auto-generated catch block } } } }
客户端代码:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.InetAddress; import java.net.Socket; public class Client{ /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { String ip = InetAddress.getLocalHost().getHostAddress(); int port = 1025; Socket socket = new Socket(ip, port); new Thread(new ClientReceiver(socket)).start(); PrintStream pw = new PrintStream(socket.getOutputStream()); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String msg = null; while ((msg = br.readLine()) != null) { pw.println(msg); } } public static class ClientReceiver implements Runnable { Socket client; BufferedReader br; public ClientReceiver(Socket client) throws IOException { this.client = client; br = new BufferedReader(new InputStreamReader( client.getInputStream())); } public void run() { // TODO Auto-generated method stub try { String msg = null; while ((msg = br.readLine()) != null) { System.out.println(msg); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
运行:
- 先运行Server再运行Client
- 在Client中输入你要传送的文字。
以上是堵塞式网络通信,所以需要创建线程去完成。非堵塞式NIO可以让服务器使用一个或有限几个线程来同时处理连接到服务器上的所有客户端。
通道Socket:
在NIO涉及到几个重要的:Channel(通道),Buffer(缓冲区),Selector(选择器)。
NIO是双向的,需要Channel来完成读写操作,然而Channel是由Selector来管理。 读取或写入的,为了高效操做读写用到Buffer。
通过SocketChannel,以TCP来向网络连接的两端读写数据;
通过ServerSocketChanel能够监听客户端发起的TCP连接,并为每个TCP连接创建一个新的SocketChannel来进行数据读写;
通过DatagramChannel,以UDP协议来向网络连接的两端读写数据。
实例:
服务端代码:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class MyNioServer { protected Selector selector; static int BufferSize = 2 * 1024; protected ByteBuffer clientBuffer = ByteBuffer.allocate(BufferSize); ServerSocketChannel server; public MyNioServer(int port) throws IOException { selector = this.getSelector(port); } // 获取Selector protected Selector getSelector(int port) throws IOException { Selector sel = Selector.open(); server = ServerSocketChannel.open(); server.socket().bind(new InetSocketAddress("localhost", port)); server.configureBlocking(false); server.register(sel, SelectionKey.OP_ACCEPT); return sel; } // 监听端口 public void listen() { try { /* * 我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。 * 当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。该方法必须首先执行。 */ while (selector.select() > 0) { Iterator iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { /* * 在处理 SelectionKey 之后,我们必须首先将处理过的SelectionKey 从选定的键集合中删除。 * 如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。 * 我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey:iter.remove(); */ SelectionKey key = (SelectionKey) iter.next(); iter.remove(); process(key); } } } catch (IOException e) { e.printStackTrace(); } } // 处理事件 protected void process(SelectionKey key) throws IOException { if (key.isAcceptable()) { // 接收请求 SocketChannel channel = server.accept(); // 设置非阻塞模式 channel.configureBlocking(false); //注册读动作 channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 读信息 SocketChannel channel = (SocketChannel) key.channel(); if (channel.read(clientBuffer) > 0) { // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为0 clientBuffer.flip(); byte[] data = clientBuffer.array(); System.out.println(new String(data).trim()); clientBuffer.clear(); } //注册写动作 SelectionKey sKey = channel.register(selector,SelectionKey.OP_WRITE); //传递写对应的附件(值) sKey.attach("hello! i am server!"); } else if (key.isWritable()) { // 写信息 SocketChannel channel = (SocketChannel) key.channel(); String name = (String) key.attachment(); clientBuffer.put(name.getBytes()); clientBuffer.flip(); channel.write(clientBuffer); clientBuffer.clear(); // 不加close会无限循环 channel.close(); } } public static void main(String[] args) { int port = 8888; try { MyNioServer server = new MyNioServer(port); server.listen(); } catch (IOException e) { e.printStackTrace(); } } }
客户端代码:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class MyNioClient { static InetSocketAddress ip = new InetSocketAddress("localhost", 8888); static class Message implements Runnable { String msg = ""; public Message(String msg) { this.msg = msg; } public void run() { try { // 打开Socket通道 SocketChannel client = SocketChannel.open(); // 打开选择器 Selector selector = Selector.open(); // 设置为非阻塞模式 client.configureBlocking(false); // 注册连接服务端socket动作 client.register(selector, SelectionKey.OP_CONNECT); // 连接 client.connect(ip); // 分配内存 ByteBuffer buffer = ByteBuffer.allocate(8 * 1024); while (selector.select() > 0) { Iterator iter = selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey key = (SelectionKey) iter.next(); iter.remove(); if (key.isConnectable()) { SocketChannel channel = (SocketChannel) key .channel(); if (channel.isConnectionPending()) channel.finishConnect(); buffer.put(msg.getBytes()); buffer.flip(); channel.write(buffer); buffer.clear(); key.interestOps(SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel channel = (SocketChannel) key .channel(); buffer.clear(); int count = channel.read(buffer); if (count > 0) { buffer.flip(); msg = ""; while (buffer.remaining() > 0) { byte b = buffer.get(); msg += (char) b; } System.out.println(msg); buffer.clear(); } else { client.close(); break; } } } } } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws IOException { new Thread(new Message("hello! i am client!")).start(); } }
运行:
- 先运行Server再运行Client