Java IO 分为文件IO和网络IO,文件IO主要是操作文件的读写,网络IO则是面向socket套接字用于处理来自网络上请求..

网络套接字是计算机网络的网络节点内的一种软件结构,它用于通过网络发送和接收数据的端点(IP地址+端口)。
通用的处理逻辑如下所示:

  • 使用 socket 创建socket
  • 使用 bind 绑定到一个本地地址支持其他socket使用 connect 来链接
  • 使用 listen 接受即将到来的链接和一个限制链接数的队列.
  • 使用 accept 来接受链接

简单实例

  1. public class Test {
  2. final Logger log = LoggerFactory.getLogger(this.getClass());
  3. public static void main(String[] args) throws Exception {
  4. ServerSocket socket = new ServerSocket();
  5. socket.bind(new InetSocketAddress("0.0.0.0", 9999));
  6. while (true) {
  7. handle(socket.accept());
  8. }
  9. }
  10. static void handle(Socket socket) throws IOException {
  11. final PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
  12. final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  13. String inputLine;
  14. while ((inputLine = bufferedReader.readLine()) != null) {
  15. if (".".equals(inputLine)) {
  16. printWriter.println("bye");
  17. break;
  18. }
  19. printWriter.println(inputLine);
  20. printWriter.flush();
  21. }
  22. printWriter.close();
  23. bufferedReader.close();
  24. socket.close();
  25. }
  26. }
  • ServerSocket ,代表服务端套接字,等待请求到来,基于请求处理并响应
  • InetSocketAddress 代表了IP套接字地址(IP地址+端口)
  • create 创建一个 socket
  • bind 一个IP和端口并且 监听
  • accept 该方法将阻塞,直到建立连接为止
  • 从socket中读取数据或者写入数据到socket中. 接受到 . 后关闭链接.

source code ./src/solaris/native/java/net/PlainSocketImpl.c

源代码

  1. public void bind(SocketAddress endpoint, int backlog) throws IOException {
  2. try {
  3. //getImpl()
  4. //--- 内部执行socketCreate,系统调用 https://linux.die.net/man/2/socket
  5. //check ...
  6. //JNI调用后执行系统调用(system call) bind
  7. //java.net.PlainSocketImpl.socketBind
  8. getImpl().bind(epoint.getAddress(), epoint.getPort());
  9. //JNI调用执行系统调用(listen)
  10. //java.net.PlainSocketImpl.socketListen
  11. getImpl().listen(backlog);
  12. bound = true;
  13. } catch(SecurityException e) {
  14. bound = false;
  15. throw e;
  16. } catch(IOException e) {
  17. bound = false;
  18. throw e;
  19. }
  20. }

java.net.PlainSocketImpl.socketBind

+552 PlainSocketImpl.c

  1. /*
  2. * Class: java_net_PlainSocketImpl
  3. * Method: socketBind
  4. * Signature: (Ljava/net/InetAddress;I)V
  5. */
  6. JNIEXPORT void JNICALL
  7. Java_java_net_PlainSocketImpl_socketBind(JNIEnv *env, jobject this,
  8. jobject iaObj, jint localport) {
  9. /* fdObj is the FileDescriptor field on this */
  10. jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
  11. /* fd is an int field on fdObj */
  12. int fd;
  13. int len;
  14. SOCKADDR him;
  15. if (IS_NULL(fdObj)) {
  16. JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
  17. "Socket closed");
  18. return;
  19. } else {
  20. fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
  21. }
  22. if (IS_NULL(iaObj)) {
  23. JNU_ThrowNullPointerException(env, "iaObj is null.");
  24. return;
  25. }
  26. /* bind */
  27. if (NET_InetAddressToSockaddr(env, iaObj, localport, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) {
  28. return;
  29. }
  30. setDefaultScopeID(env, (struct sockaddr *)&him);
  31. if (NET_Bind(fd, (struct sockaddr *)&him, len) < 0) {
  32. if (errno == EADDRINUSE || errno == EADDRNOTAVAIL ||
  33. errno == EPERM || errno == EACCES) {
  34. NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "BindException",
  35. "Bind failed");
  36. } else {
  37. NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
  38. "Bind failed");
  39. }
  40. return;
  41. }
  42. /* set the address */
  43. (*env)->SetObjectField(env, this, psi_addressID, iaObj);
  44. /* initialize the local port */
  45. if (localport == 0) {
  46. /* Now that we're a connected socket, let's extract the port number
  47. * that the system chose for us and store it in the Socket object.
  48. */
  49. if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) {
  50. NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
  51. "Error getting socket name");
  52. return;
  53. }
  54. localport = NET_GetPortFromSockaddr((struct sockaddr *)&him);
  55. (*env)->SetIntField(env, this, psi_localportID, localport);
  56. } else {
  57. (*env)->SetIntField(env, this, psi_localportID, localport);
  58. }
  59. }
  1. /*
  2. * Wrapper for bind system call - performs any necessary pre/post
  3. * processing to deal with OS specific issues :-
  4. *
  5. * Linux allows a socket to bind to 127.0.0.255 which must be
  6. * caught.
  7. *
  8. * On Solaris with IPv6 enabled we must use an exclusive
  9. * bind to guarantee a unique port number across the IPv4 and
  10. * IPv6 port spaces.
  11. *
  12. */
  13. int
  14. NET_Bind(int fd, struct sockaddr *him, int len)
  15. {
  16. //... linux
  17. rv = bind(fd, him, len);
  18. return rv;
  19. }

java.net.PlainSocketImpl.socketAccept

  1. //-- fcntl - manipulate file descriptor
  2. #define SET_BLOCKING(fd) { \
  3. int flags = fcntl(fd, F_GETFL); \
  4. flags &= ~O_NONBLOCK; \
  5. fcntl(fd, F_SETFL, flags); \
  6. }
  7. JNIEXPORT void JNICALL
  8. Java_java_net_PlainSocketImpl_socketAccept(JNIEnv *env, jobject this,
  9. jobject socket)
  10. {
  11. //填充参数
  12. for (;;) {
  13. int ret;
  14. //check and accept 系统调用
  15. newfd = NET_Accept(fd, (struct sockaddr *)&him, (jint*)&len);
  16. /* connection accepted */
  17. if (newfd >= 0) {
  18. //设置阻塞 内部是
  19. SET_BLOCKING(newfd);
  20. break;
  21. }
  22. /* non (ECONNABORTED or EWOULDBLOCK) error */
  23. if (!(errno == ECONNABORTED || errno == EWOULDBLOCK)) {
  24. break;
  25. }
  26. //check
  27. }
  28. //check
  29. /*
  30. * 在新socket结构中填充remote 端口和地址
  31. */
  32. socketAddressObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&him, &port);
  33. if (socketAddressObj == NULL) {
  34. /* should be pending exception */
  35. close(newfd);
  36. return;
  37. }
  38. /*
  39. * Populate SocketImpl.fd.fd
  40. * 填充文件描述符
  41. */
  42. socketFdObj = (*env)->GetObjectField(env, socket, psi_fdID);
  43. (*env)->SetIntField(env, socketFdObj, IO_fd_fdID, newfd);
  44. (*env)->SetObjectField(env, socket, psi_addressID, socketAddressObj);
  45. (*env)->SetIntField(env, socket, psi_portID, port);
  46. /* also fill up the local port information */
  47. port = (*env)->GetIntField(env, this, psi_localportID);
  48. (*env)->SetIntField(env, socket, psi_localportID, port);
  49. }

在linux中,一切都是文件,操作socket也是读写文件,所以在经历bind/accept等经历后,可以在serverSocket和socket上的文件描述符进行读写.

参考链接