套接字Socket编程

网络编程,主要是指基于TCP/UDP的网络通信编程,使用Socket类实现,也称为socket编程。

Socket编程进行的是端到端的通信,在网络层,Socket函数需要指定到底是IPv4还是IPv6,分别对应设置为AF_INET和AF_INET6。另外还需要指定到底是TCP还是UDP。TCP是基于数据流的,所以设置为SOCK_STREAM,而UDP是基于数据报的,因而设置为SOCK_DGRAM.

基于 TCP 协议的 Socket 程序函数调用过程

两端创建了 Socket 之后,接下来的过程中,TCP 和 UDP 稍有不同,我们先来看 TCP。

TCP 的服务端要先监听一个端口,一般是先调用 bind 函数,给这个 Socket 赋予一个 IP 地址和端口。为什么需要端口呢?要知道,你写的是一个应用程序,当一个网络包来的时候,内核要通过 TCP 头里面的这个端口,来找到你这个应用程序,把包给你。为什么要 IP 地址呢?有时候,一台机器会有多个网卡,也就会有多个 IP 地址,你可以选择监听所有的网卡,也可以选择监听一个网卡,这样,只有发给这个网卡的包,才会给你。

当服务端有了 IP 和端口号,就可以调用 listen 函数进行监听。在 TCP 的状态图里面,有一个 listen 状态,当调用这个函数之后,服务端就进入了这个状态,这个时候客户端就可以发起连接了。

在内核中,为每个 Socket 维护两个队列。一个是已经建立了连接的队列,这时候连接三次握手已经完毕,处于 established 状态;一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于 syn_rcvd 的状态。

接下来,服务端调用 accept 函数,拿出一个已经完成的连接进行处理。如果还没有完成,就要等着。

在服务端等待的时候,客户端可以通过 connect 函数发起连接。先在参数中指明要连接的 IP 地址和端口号,然后开始发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的 accept 就会返回另一个 Socket。

监听的 Socket 和真正用来传数据的 Socket 是两个,一个叫作监听 Socket,一个叫作已连接 Socket。

连接建立成功之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

下图是基于TCP协议Socket程序函数调用过程

基于TCP的Socket通讯(测试代码):

服务端代码:

/*
 *  把从客户端读取到的一行数据的字符进行翻转,然后发送给客户端
 *  当读取到over时,连接断开
 */

public class TcpService
{
	public static void main(String[] args)
	{

		try {
			ServerSocket server = new ServerSocket(10002);

			while (true) {
				Socket socket = server.accept();
				MyThread myThread = new MyThread(socket);
				myThread.start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

class MyThread extends Thread
{
	private Socket socket;

	public MyThread(Socket socket)
	{
		this.socket = socket;
	}

	@Override
	public void run()
	{
		try {
			BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			String line = null;
			while ((line = bufferedReader.readLine()) != null) {
				if ("over".equalsIgnoreCase(line)) {
					break;
				}

				// 字符翻转的操作
				char[] chs = line.toCharArray();

				for (int i = 0; i < chs.length / 2; i++) {
					char ch = chs[i];
					chs[i] = chs[chs.length - 1 - i];
					chs[chs.length - 1 - i] = ch;
				}

				bufferedWriter.write(chs);
				bufferedWriter.newLine();
				bufferedWriter.flush();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}
}

客户端代码:

public class TcpClient
{
	public static void main(String[] args)
	{
		Socket socket = null;
		Scanner scanner = null;
		try {
			socket = new Socket("localhost", 10002);
			BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

			scanner = new Scanner(System.in);
			String line = null;
			while ((line = scanner.nextLine()) != null) {
				bufferedWriter.write(line);
				bufferedWriter.newLine();
				bufferedWriter.flush();
				line = bufferedReader.readLine();
				if (line == null) {
					break;
				}
				System.out.println(line);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (socket != null) {
					socket.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (scanner != null) {
				scanner.close();
			}
		}
	}
}

这是一个字符串翻转的例子先运行服务端,再运行客户端。当在console中输入over字符的时候程序结束。

基于 UDP 协议的 Socket 程序函数调用过程

对于 UDP 来讲,过程有些不一样。UDP 是没有连接的,所以不需要三次握手,也就不需要调用 listen 和 connect,但是,UDP 的的交互仍然需要 IP 和端口号,因而也需要 bind。UDP 是没有维护连接状态的,因而不需要每对连接建立一组 Socket,而是只要有一个 Socket,就能够和多个客户端通信。也正是因为没有连接状态,每次通信的时候,都调用 sendto 和 recvfrom,都可以传入 IP 地址和端口。

这个图的内容就是基于 UDP 协议的 Socket 程序函数调用过程。

 

UDP编程的时候没有典型的服务器——客户端结构,而且通信的两端不建立连接,可以直接把数据封装成数据包发送到另一端,而且每一端都可以发送、接收数据包

DatagramSocket 表示通信的一端,可以发送、接收数据包

DatagramPacket 数据包,理论上一个数据包可包含的数据量最多为65535字节

public class UDPSend
{
	public static void main(String[] args) throws IOException
	{
		DatagramSocket sendSocket = new DatagramSocket(10003);
		byte[] data = "hello".getBytes();
		DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 10004);
		sendSocket.send(packet);
		sendSocket.close();
	}
}
public class UDPReceive
{
	public static void main(String[] args) throws IOException
	{
		DatagramSocket receiveSocket = new DatagramSocket(10004);
		byte[] buff = new byte[1024];
		DatagramPacket packet = new DatagramPacket(buff, buff.length);
		receiveSocket.receive(packet);
		int len = packet.getLength();
		System.out.println(new String(buff, 0, len));
		receiveSocket.close();
	}
}

 

猜你喜欢

转载自blog.csdn.net/qq_35716892/article/details/81675863