socket基础
对于java网络编程来说,我们能接触到的最底层便是socket了。我相信大部分阅读此篇文章的同仁都用过socket。但是,我这篇教程的理念之一就是事无巨细,或者说,啰里啰嗦。因为本来就是一个思维的过程,所以,看官老爷们,就当做意识流风格来看吧(也是因为我文笔较差,因此文章的观赏性可能不是太好,给自己找个理由)。
我在刚学编程的时候,其实是一脸茫然的,虽然也会为屏幕输出“hello world",感到兴奋,但是兴奋之余会觉得,嗯~?,这东西跟我想象的不一样啊,就一个黑屏上输出几个字符,能干啥?这玩意跟网站啥的不沾边啊。包括学习了一些算法、数据结构后,还是觉得,网站啥的,跟我学的不沾边。
后来在学习参加了一个项目,涉及到网络编程,项目简单点说就是树莓派跟pc通过路由器进行数据交换。从那时候起开始使用socket,当然那个时候不求甚解,根本跟学过的网络模型也对不上号。什么三次握手,四次挥手,没用到啊。先不管,干就完了。
秉着这个徐循渐进的学习及思考过程,我就开始了socket编程。
链接
来一段服务端socket示例:
代码 1-1
public class OioServer {
private ServerSocket serverSocket;
private void openServer(int port) throws IOException {
// 1 创建ServerSocket
serverSocket = new ServerSocket();
// 2 绑定端口
SocketAddress socketAddress = new InetSocketAddress(port);
serverSocket.bind(socketAddress);
}
@Test
public void testOpenServer() throws IOException {
OioServer oioServer = new OioServer();
oioServer.openServer(8081);
// block
Scanner scan = new Scanner(System.in);
scan.next();
}
}
运行test后,服务器开始监听port
端口了。然后我们验证下是不是真的在监听,我再window上测试的,所以这里以window为例。首先要打开cmd窗口,然后输入命令
代码 1-2
netstat -aon|findstr "8081"
图1-1
可以看到真的在监听了, 现在虽然开始监听了,但是,真的有客户端从8081进来该怎么办?我们还需要处理连接,这个连接就是我们的主角socket
.
修改1-1代码如下:
代码1-2
public class OioServer {
private ServerSocket serverSocket;
private void openServer(int port) throws IOException {
// 1 创建ServerSocket
serverSocket = new ServerSocket();
// 2 绑定端口
SocketAddress socketAddress = new InetSocketAddress(port);
serverSocket.bind(socketAddress);
}
@Test
public void testOpenServer() throws IOException {
OioServer oioServer = new OioServer();
oioServer.openServer(8081);
Socket socket = oioServer.listenAccept();
}
}
此时,服务端就运行有客户端进来连接了。然后我们再写一个客户端。
代码 1-3
public class OioClient{
public Socket connect(String host, int port) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port));
return socket
}
@Test
public void testClient() throws IOException, InterruptedException {
OioClient oioServer = new OioClient();
oioServer.connect("127.0.0.1", 8081);
// block
Scanner scan = new Scanner(System.in);
scan.next();
}
}
运行test,为例不让test退出,我们用标准出入block住test线程。记住,现在我们服务端还在监听,我服务端和客户端都跑在同一台pc上。效果如图
图1-2
可以看到第二行本地地址是127.0.0.1:6283
远程地址是127.0.0.1:8081
,那我们是不是可以猜到,这个TCP连接是客户端的,客户端的IP是6283
,我们虽然没指定,但是系统会给我们随机分配一个可用的端口,不信可以试一下,下次可能就不一样了。
顺理成章,第三行就是我们服务端的TCP连接。
那么我们是不是可以有一个这样的结论:socket
其实就是一个TCP连接的封装(当然也可以是UDP,这里不做讨论)?或者socket的下层是TCP/IP层?
到现在我们大概知道了socket跟TCP的联系,既跟网络模型的联系。
有了TCP链接,我们就可以开始传输数据了。很多网上例子是,简历socket连接后,客户端给服务器发送数据,然后服务器接收数据并回复。但是请不要误解,过程不一定全部是这样的。我们客户端和服务端建立链接后,两边都得到一个socket
,这两个socket
是等价的。如果你对TCP有了解的话,一定会知道TCP的四次挥手,网上关于四次挥手的图大概也都是下图这个样子:
图1-3
但是呢,四次挥手同样可以由服务端发起。因此,就算是在TCP层,两个socket的是等价的。
通讯
我们得到活跃的socket后,就可以对socket进行读写操作了。既然socket没什么区别,那我们可以无差异对待socket。我们来写socket读写取的方法:
代码1-4
public class SocketUtils {
/**
* 从socket中读数据
*/
public static String read(Socket socket) {
try {
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
//注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
sb.append(new String(bytes, 0, len, "UTF-8"));
}
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 往socket中写数据
*/
public static void write(Socket socket, String response) {
try {
OutputStream outputStream = socket.getOutputStream();
outputStream.write(response.getBytes("UTF-8"));
outputStream.flush();
socket.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面代码以入参的形势将socket传入,然后就行读写,注意,我们这两个并没有调用方法socket.close
,也就是说socket可以重复读写数据,而网上大部分教程直接会吧socket给关闭,造成初学者以为每次读写玩就得关闭socket的错觉。
在方法read
中,根据(len = inputStream.read(bytes)) != -1
来判断是否读取完数据。而写入数据端怎么告诉接收端它完成了写入呢?我们可以用socket.shutdownOutput()
方法。
好,接下来我们就来测试下发送与接收数据。
代码1-4
public class OioServer {
...
@Test
public void testOpenServer() throws IOException {
OioServer oioServer = new OioServer();
oioServer.openServer(8081);
Socket socket = oioServer.listenAccept();
SocketUtils.write(socket, "Can you hear me?");
String msg = SocketUtils.read(socket);
System.out.println(msg);
// block
Scanner scan = new Scanner(System.in);
scan.next();
}
}
代码1-5
public class OioClient {
...
@Test
public void testClient() throws IOException, InterruptedException {
OioClient oioServer = new OioClient();
Socket socket = oioServer.connect("127.0.0.1", 8081);
String msg = SocketUtils.read(socket);
System.out.println(msg);
SocketUtils.write(socket, "Yes, I can hear you!");
// block
Scanner scan = new Scanner(System.in);
scan.next();
}
}
先执行testOpenServer()
再执行testClient()
。就会完成对话。当然我们可以重复调SocketUtils.read
和 SocketUtils.write
进行通讯,只要两边socket不关闭。
小结
此篇文章总结了Socket的一些使用,包括服务器启动,socket建立,并证明了socket的等价性
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。