网络编程套接字

预备

理解源IP地址和目的IP地址

在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址

有了IP地址能够把消息发送到对方的机器上, 但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析.

认识端口号

端口号(port)是传输层协议的内容.

  • 端口号是一个32位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用

理解 “端口号” 和 “进程ID”

pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程.

一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;

理解源端口号和目的端口号

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发 给谁”;

认识TCP协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

认识UDP协议

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

Socket编程接口

socket 常见API

DatagramSocket类:

方法签名方法说明
DatagramSocket(int port,InetAddress laddr)创建一个数据报套接字,绑定到指定的本地地址
DatagramSocket(SocketAddress bindaddr)创建一个数据报套接字,绑定到指定的本地套接字地址
void bind(SocketAddress addr)将此DatagramSocket绑定到特定的地址和端口
void connect(InetAddress address, int port)将套接字连接到此套接字的远程地址
void receive(DatagramPacket p)从此套接字接收数据报包
void close()关闭此数据报套接字
void send(DatagramPacket p)从此套接字发送数据报包

简单的UDP网络程序

实现一个简单的服务端接收客户端输入的信息然后在服务端显示。
UDP服务端

package com.store.frank.udp;
import java.net.DatagramPacket; 
import java.net.DatagramSocket; 
import java.net.SocketAddress;
public class UdpServer {
    public static void main(String[] args) throws Exception {        
    	new UdpServer().start();    
    }
    // 定义udp端口号    
    public static final int PORT = 30000;    
    // 每个数据报最大为4K    
    private static final int DATA_LEN = 4096;    
    // 接收网络数据的字节数组    
    byte[] buff_in = new byte[DATA_LEN];    
    // 以指定字节数组创建准备接受数据的DatagramPacket对象    
    private DatagramPacket packet_in = new DatagramPacket(buff_in, buff_in.length);    
    // 定义一个用于发送的DatagramPacket对象    
    private DatagramPacket packet_out;
    public void start() throws Exception {        
    	try(DatagramSocket socket = new DatagramSocket(PORT)){            
    		String word = null;  
    		// 客户端输入的英文单词            
    		SocketAddress address=null; 
    		// 获取客户端对象,通过该对象将数据写给对方,否则没有目的地            
    		byte[] reviceData=null; 
    		// 返回给客户端的数据            
    		System.out.println("服务端启动了......");            
    		while (true){               
    			// 读取socket中的数据,然后将数据封装到 packet_in 中                
    			socket.receive(packet_in);               
    			// 获取客户端输入的数据                
    			buff_in=packet_in.getData();               
    			// 将byte数组转换为字符串  需要去掉后面的空格                
    			word=new String(buff_in,0,buff_in.length).trim();                
    			System.out.println("客户端输入的是:"+word);                
    			address=packet_in.getSocketAddress();                
    			// 检查退出条件                
    			if("down".equals(word)){                    
    				reviceData="服务器关闭了,请重试".getBytes();                    
    				packet_out=new DatagramPacket(reviceData,reviceData.length,address);                    
    				socket.send(packet_out);                    
    				break;                
    			}else {                 
    				// 构建服务端发送给客户端的数据                    
    				reviceData=("你输入的英文单词是:"+word).getBytes();                 
    				// 构建 packet_out 发送数据                    
    				packet_out=new DatagramPacket(reviceData,reviceData.length,address);                    
    				socket.send(packet_out);                
    			}            
    		}        
    	} catch (Exception e) {            
    		e.printStackTrace();            
    		System.out.println("=====服务端关闭=====");        
    	}    
    } 
} 

UDP服务客户端

package com.store.frank.udp;
import java.io.IOException; 
import java.net.DatagramPacket; 
import java.net.DatagramSocket; 
import java.net.InetAddress; 
import java.util.Scanner;

public class UdpClient {    
// 定义发送数据报的目的地      
    public static final int DEST_PORT = 30000;
    public static final String DEST_IP = "127.0.0.1";    // 定义每个数据报的最大大小为4K    
    private static final int DATA_LEN = 4096;    
    // 定义接收网络数据的字节数组    
    byte[] inBuff = new byte[DATA_LEN];
    // 创建接受回复数据的DatagramPacket对象    
    private DatagramPacket packet_in = new DatagramPacket(inBuff, inBuff.length);   
// 定义一个用于发送的DatagramPacket对象    
    private DatagramPacket packet_out = null;
    public void start() throws IOException {        
    	try (DatagramSocket socket = new DatagramSocket()) {            
    		// 初始化发送用的DatagramSocket            
    		InetAddress ip = InetAddress.getByName(DEST_IP);            
    		packet_out = new DatagramPacket(new byte[0], 0, ip, DEST_PORT);            
    		// 创建键盘输入流            
    		Scanner sc = new Scanner(System.in);            System.out.println("请输入数据");            
    		// 不断读取键盘输入            
    		String key =null;            
    		// 键盘输入字符对应的byte数组            
    		byte[] keyBuff = null;            
    		while (sc.hasNextLine()) {                
    			key = sc.nextLine(); 
    			if ("exit".equals(key)) {                    
    				break;                
    			}                
    			// 输入的字符串→字节数组                
    			keyBuff = key.getBytes();                
    			// 设置发送用的DatagramPacket里的字节数据                
    			packet_out.setData(keyBuff);                
    			// 发送数据报                
    			socket.send(packet_out);                
    			// 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。                
    			socket.receive(packet_in);                
    			System.out.println(new String(inBuff, 0, packet_in.getLength()));                
    			System.out.println("请输入数据:");            
    		}            
    	System.out.println("=== 客户端退出 ===");        
       }    
  }
    public static void main(String[] args) throws IOException {        
    	new UdpClient().start();    
    	} 
}

简单的TCP网络程序

TCP socket API 详解
ServerSocket类:

方法签名方法说明
ServerSocket(int port)创建绑定到指定端口的服务器套接字
ServerSocket(int port, int backlog)创建服务器套接字并将其绑定到指定的本地端口号,并指定了积压。
Socket accept()侦听要连接到此套接字并接受它
bind(SocketAddress endpoint)将ServerSocket绑定到特定地址(IP地址和端口号)
InetAddress getInetAddress()返回此服务器套接字的本地地址
void close()关闭此套接字
int getLocalPort()返回此套接字正在侦听的端口号

bind():
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后 就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号; 如果地址为 null ,则系统将接收临时端口和有效的本地地址来绑定套接字.
accept():
三次握手完成后, 服务器调用accept()接受连接; 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来; Socket 是一个返回值,代表网络的套接字.

Socket类

方法签名方法说明
Socket(InetAddress address, int port)创建流套接字并将其连接到指定IP地址的指定端口号
Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号
void bind(SocketAddress bindpoint)将套接字绑定到本地地址
void connect(SocketAddress endpoint)将此套接字连接到服务器
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

TCP通用服务器
TCP服务端,接收客户端内容,然后输出

package com.store.frank.http;
import java.io.BufferedReader; 
import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket;
public class HttpServer {
    public static void main(String[] args) throws IOException {        
    	ServerSocket server = new ServerSocket(8888);        System.out.println("服务器启动了....");        
    	boolean isShow=true; 
    	// 设置第一次输出[服务器启动了....],启动后不再显示。        
    	while (true){            
    		try {                
    			Socket client = server.accept();                String 	
    			clientName=client.getInetAddress().getLocalHost().toString();                
    			if(isShow){                    
    				System.out.println("客户端:"+clientName+"已连接到服务器");                
    			}                
    			isShow=false;                
    			BufferedReader br = new BufferedReader(new 
			InputStreamReader(client.getInputStream()));                
			String word = br.readLine(); 
			// word 客户端发送来的消息                
			if(null != word){                    
				System.out.println("客户端输入的是:"+word+",服务器响应的是:"+word);                    
				BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));                    
				bw.write(word+"\n");                    
				bw.flush();                
			}            
		} catch (IOException e) {                
			e.printStackTrace();            
		}        
	}    
   } 
}

TCP客户端

package com.store.frank.http; 
 
import java.io.BufferedReader; 
import java.io.BufferedWriter; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStream; 
import java.io.OutputStreamWriter; 
import java.net.Socket; 
import java.util.Scanner; 
 
 public class HttpClient { 
 
    public static void main(String[] args) {         
    	try {             
    		Socket s = new Socket("127.0.0.1",8888);             
    		//构建IO             
    		InputStream is = s.getInputStream();             
    		OutputStream os = s.getOutputStream();             
    		BufferedWriter bw = new BufferedWriter(new 
    		OutputStreamWriter(os));             
    		BufferedReader br = new BufferedReader(new 
    		InputStreamReader(is));             
    		Scanner scanner=new Scanner(System.in);             
    		String key = null;             
    		String value = null;             
    		while (true){                 
    			System.out.println("请输入数据");                 
    			if(scanner.hasNext()) {                     
    				key = scanner.nextLine();                     
    				//向服务器端发送一条消息                     
    				bw.write(key+"\n");                     
    				bw.flush();                     
    				//读取服务器返回的消息                     
    				value = br.readLine();                     
    				System.out.println("客户端输入的是:"+key+",服务器响应的是:"+value);                 
    			}             
    		}         
    	} catch (Exception e) {             
    		e.printStackTrace();         
    	}     
    } 
}

多线程版
ServerThread 类为多线程类,StringServer类为调用类
…线程池版

线程池 优点

  • 利用线程池管理并复用线程、控制最大并发数等。
  • 实现任务线程队列缓存策略和拒绝机制。
  • 实现某些与时间相关的功能,如定时执行、周期执行等。
  • 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明 显然要大.因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免个服务线程互相影响。

线程池 缺点

  • 前期需要创建多个线程示例对象。
  • 如果客户端连接少,会造成线程资源浪费。

TCP/UDP对比

  • 可靠传输 vs 不可靠传输
  • 有连接 vs 无连接
  • 字节流 vs 数据报
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值