实验介绍
本次实验使用Java语言,使用Java Socket以tcp协议实现客户端和服务器端的通信。
什么是socket通信?
在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。socket通信是基于应用服务与TCP/IP通信之间的一个抽象,它将TCP/IP协议里面复杂的通信逻辑进行分装,对用户来说,只要通过一组简单的API就可以实现网络的连接。Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。Socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。
套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
2、客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
什么是TCP?
TCP是(Tranfer Control Protocol)的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
实验过程
Java Socket通信流程
在服务器端Listen()监听某个端口是否有连接请求,Client端向Server 端发出连接请求,Server端向Client端发回Accept接受消息。这样一个连接就建立起来了。Server端和Client端都可以通过Send,Write等方法与对方通信。本次代码实现逻辑:
1、创建Socket;
2、 打开连接到Socket的输入/出流;
3、按照一定的协议对Socket进行读/写操作;
4、关闭Socket。
详细代码:
客户端:
/** * */ package Tcptest; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; /** * @author 86183 * */ public class TcpClient { public static void main(String[] args) throws UnknownHostException, IOException { String ipString = new String("127.0.0.1"); Socket socket = new Socket(ipString,9999);//建立socket,赋值IP地址和端口号 try { while (true) { OutputStream outputStream = socket.getOutputStream();//获取socket流中的输出流 ///Scanner scanner = new Scanner(System.in); System.out.println("client发送:hello!"); ///String next = scanner.next(); String next=new String("hello"); outputStream.write(next.getBytes()); //System.out.println("client接收:"); InputStream inputStream = socket.getInputStream(); byte [] buf = new byte[1024]; int len = inputStream.read(buf); String str = new String(buf, 0, len); System.out.println("client接收:"+str); } } catch (Exception e) { // TODO: handle exception }finally { socket.close();//关闭连接 } } }
服务器端:
/** * */ package Tcptest; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.nio.channels.AcceptPendingException; import java.util.Scanner; /** * @author 86183 * */ public class TcpServer { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // TODO 自动生成的方法存根 System.out.println("服务器端等待建立连接。。。"); ServerSocket serverSocket = new ServerSocket(9999); Socket accept = serverSocket.accept(); System.out.println("连接成功!"); /*String hostAddress = accept.getInetAddress().getHostAddress(); int port = accept.getLocalPort(); System.out.println("发送端ip"+hostAddress+"端口号"+port);*/ try { while (true) { InputStream inputStream = accept.getInputStream(); //System.out.println("server接收:"); byte [] buf = new byte[1024]; int len = inputStream.read(buf); String str = new String(buf, 0, len); System.out.print("服务器接收到信息:"); System.out.println(str); System.out.println("server接收:"+str); //str=str.toUpperCase(); OutputStream outputStream = accept.getOutputStream(); System.out.println("server回复:hi"); //Scanner scanner = new Scanner(System.in); String strnext = new String("hi!"); outputStream.write(strnext.getBytes()); } } catch (Exception e) { // TODO: handle exception }finally { accept.close(); serverSocket.close(); } } }
实验效果
实验分析(分析Java Socket API与Linux Socket API之间的关系)
java的socket实现是通过调用操作系统的socket api实现的,下面图表展示其调用关系
从这张图我们基本可以看出在服务器端,Java在socket通信时,将socket(),bind(),listen()方法封装到一个serversocket方法中,从而隐藏了服务器绑定端口,和侦听端口的细节,当Java在服务器端new 一个serverSocket对象时,使用构造方法,服务器就调用底层操作系统的socket(),bind(),listen()方法,创建,绑定并侦听端口。在Java中同样使用socket对象的accept方法来建立连接,并调用底层操作系统的accept()函数,在Java中通过流对象获取通信信息,此时,Java调用底层操作系统的recv()函数获取通信信息。在客户端,Java使用socket创建对象,同样隐藏了操作系统connect()的细节,Java将Linux网络接口的许多方法进一步封装,编程更加简便,代码更加简洁。
Linux socket api
socket()
#include#include int socket(int domain, int type, int protocol);
创建一个sockfd,以文件描述符的形式作为网络实体抽象的操作接口,包括unix domain socket, internet domain socket等不同场景下,可以按照文件读写方式来收发数据。成功返回socket文件描述符;失败返回 -1, 并设置errno。
bind()
#include#include int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
将一个socket地址绑定到socket文件描述符上。客户端socket文件描述符通常不需要绑定socket地址,采用匿名方式即可。成功时返回0, 失败时返回-1并设置errno。
listen()
#includeint listen(int sockfd, int backlog);
在内核创建最大长度为backlog的监听队列。成功时返回0, 失败则返回-1, 并设置errno。
accept()
#include#include int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
从内核的连接队列中获取已经完成三次握手的完成连接,并返回新的连接sockfd。服务器端可通过对该文件描述符的读写来完成与客户端的通信。成功时返回新的连接socket文件描述符;失败则返回-1,并设置errno。
connect()
#include#include int connect(int sockfd, const struct sockaddr * serv_addr, socklen_t addrlen);
当三次握手中,客户端收到server-syn及local-syn-ack的合并报文段时,就可以返回了。表明sockfd已经可写。成功时返回0,且sockfd唯一标志了该连接,客户端通过读写该sockfd来与服务器端通信;失败时返回-1,并设置errno。
close()
#include#include int close(int sockfd); int shutdown(int sockfd, int hwoto);
close函数会将本地sockfd的读写两个方向全部关闭,并将sockfd的引用计数减一。
shutdown可以分别对sockfd读端或者写端单独关闭;
参考资料:
https://blog.csdn.net/alpslover/article/details/80387140
https://blog.csdn.net/u014209205/article/details/80461122
https://www.jianshu.com/p/cde27461c226
https://blog.csdn.net/vipshop_fin_dev/article/details/102966081