Socket编程快速入门实战
文章目录
1. Socket编程(网络编程)
在Java中,Socket为java.net包下的一个类;
中文翻译为套接字 ,套接字使用TCP提供了两台计算机之间的通信机制;客户端创建一个套接字,并尝试连接服务器的套接字;
表示为网络中两个机器的对接
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流;
该通信是一个全双工通信方式,即TCP是一个双向的通信协议,意思就是可以双向同时读写数据(读的同时也能写);
具体步骤
- 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信;
- 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待(阻塞方法),直到客户端连接到服务器上给定的端口;
- 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
- Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个Socket 对象能够与服务器进行通信。
- 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket;
1. ServerSocket
这是java.net包下的一个类;
1. 构造方法
它有如下几个构造方法:
- public ServerSocket(int port) throws IOException
- 创建绑定到特定端口的服务器套接字 ,一个端口代表着一个唯一对应的进程;
- public ServerSocket(int port, int backlog) throws IOException
- backlog就是代表客户端的个数,这个参数就是用来限制客户端的连接最大数量;
- public ServerSocket(int port, int backlog, InetAddress address) throws IOException
- 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
前面两个构造方法没有传入IP地址,因为上面两个构造方法用了默认的IP地址(即localhost:127.0.0)
那为什么还需要传入指定的IP地址呢?因为有的性能良好的服务器上有多个网卡(一个网卡对应一个MAC地址),所以对应多个IP地址,所以得指定哪一个IP地址来创建服务器;
就我们的笔记本就有两个网卡(一个有线网卡和一个无线网卡);
- 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
- public ServerSocket() throws IOException
- 创建非绑定服务器套接字;
服务端socket处理客户端socket连接是需要一定时间的。ServerSocket有一个队列,存放还没有来得及处理的客户端Socket,这个队列的容量就是backlog的含义。如果队列已经被客户端socket占满了,如果还有新的连接过来 ,那么ServerSocket会拒绝新的连接。也就是说backlog提供了容量限制功能,避免太多的客户端socket占用太多服务器资源;
2. accept方法
Serversoket的一个最常用的方法就是accept了;
public Socket accept() throws IOException {
if (isClosed())
throw new SocketException("Socket is closed");
if (!isBound())
throw new SocketException("Socket is not bound yet");
Socket s = new Socket((SocketImpl) null);
implAccept(s);
return s;
}
可以看到,这个方法的返回值是一个Socket对象,这个对象正是与当前连接到我这个服务端的客户端Socket对等的一个Socket,不要搞混了,这个Socket不是连接到这的客户端的Socket,而是新建的一个对等的Socket,⭐这也体现了我开始画的图中:客户端和服务端各持一个Socket;
2. Socket
这也是java.net包下的一个类;
1. 构造方法
-
public Socket(String host, int port) throws UnknownHostException, IOException
- 创建一个流套接字并将其连接到指定主机上的指定端口号。
-
public Socket(InetAddress host, int port) throws IOException
- 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
-
public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.
-
创建一个套接字并将其连接到指定远程主机上的指定远程端口。
并指定自己这个Socket使用本地的哪一个IP地址和端口号,如果指定的本地端口号被占用,就会抛出异常,而上面的是随机选择本地的端口号(0~65535),如果被占用则再选择直到找到可用的端口号为止;
一般用不到这个方法,因为没必须要指定本地哪个端口号来进行连接;
-
创建一个套接字并将其连接到指定远程主机上的指定远程端口。
-
public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.
- 创建一个套接字并将其连接到指定远程地址上的指定远程端口。
-
public Socket()
- 通过系统默认类型的 SocketImpl 创建未连接套接字 ;
2. 常用方法
-
public void connect(SocketAddress endpoint) throws IOException
将此套接字连接到指定服务器,他还有一个重载方法,还包含了一个时间参数,用来指定一个超时值;
-
public InetAddress getInetAddress()
返回套接字连接的地址;(若还没连接则返回空)
-
public SocketAddress getRemoteSocketAddress()
返回套接字连接的端点地址;
-
public InputStream getInputStream() throws IOException
返回套接字的输入流; ⭐
-
public OutputStream getOutputStream() throws IOException
返回套接字的输出流;⭐
-
public synchronized void close() throws IOException
关闭此套接字;
2. 单线程的聊天室
1. 代码
创建服务端:
public class Server {
public static void main(String[] args) throws Exception{
try {
//没有指定IP地址,默认在本地(127.0.0.0)
ServerSocket serverSocket = new ServerSocket(8000);
System.out.println("等待客户端的连接......");
//这是一个阻塞操作,没有连接会一直阻塞在这;
Socket socket = serverSocket.accept();
System.out.println("有客户连接上啦,信息如下:");
System.out.println(socket.getInetAddress()+" " + socket.getLocalPort());
//获取客户端的输入流,相当于服务端从这读取客户端发过来的信息
Scanner scanner = new Scanner(socket.getInputStream());
//scanner.useDelimiter("\n");
//获取客户端的输出流,相当于服务端从这向客户端发送的信息
PrintStream printStream = new PrintStream(socket.getOutputStream(), true);
printStream.print("hello, i am Server!!!");
if(scanner.hasNext()) {
System.out.println(scanner.next());
}
//关闭资源
scanner.close();
//printWriter.close();
serverSocket.close();
}catch (Exception e) {
System.out.println("服务端出现异常......");
}
}
}
创建客户端:
public class Client {
public static void main(String[] args) throws Exception{
Socket socket = new Socket("127.0.0.1", 8000);
System.out.println("连接上服务器了,服务器地址为:"+socket.getInetAddress());
PrintStream printStream = new PrintStream(socket.getOutputStream(), true);
printStream.print("你好呀服务器,我是客户端!!!");
//获取输入流,即读取服务器的输入
Scanner scanner = new Scanner(socket.getInputStream());
if(scanner.hasNext()) {
System.out.println("服务器说:" + scanner.next());
}
}
}
先运行服务端: (可以看到阻塞在这)
等待客户端的连接…再运行客户端:
连接上服务器了,服务器地址为:/127.0.0.1
服务器说:hello,Process finished with exit code 0
再看服务端:
等待客户端的连接…
有客户连接上啦,信息如下:
/127.0.0.1 8000
你好呀服务器,我是客户端!!!Process finished with exit code 0
2. 注意点
- 在客户端和服务端中,我们使用的打印流用的是字节打印流PrintStream,不是字符打印流PrintWriter,我刚开始就是用的PrintWriter,但是发现不能使用它(运行会阻塞在获取流那里,一直卡在那里),**我分析原因的是: **
- 我们一端使用PrintWriter进行传送给另一端信息,而另一端使用Scanner接收信息,Scanner中需要传入一个输入流,而这个输入流是InputStream,是个字节输入流,与上面的字符流并不对等,所以不行;
- 一再强调,客户端的socket和服务端通过accept得到的socket是对等的,但不是同一个对象;