流套接字: 使用傳輸層TCP協(xié)議
TCP: 即Transmission Control Protocol(傳輸控制協(xié)議),傳輸層協(xié)議。
TCP的特點(diǎn):
- 有連接
- 可靠傳輸
- 面向字節(jié)流
- 有接收緩沖區(qū),也有發(fā)送緩沖區(qū)
- 大小不限
1. ServerSocket
ServerSocket 是創(chuàng)建TCP服務(wù)端Socket的API。
注意: ServerSocket 只能用于 服務(wù)器端。
構(gòu)造方法:
方法簽名 | 方法說(shuō)明 |
---|---|
ServerSocket(int port) | 創(chuàng)建一個(gè)服務(wù)端流套接字Socket,并綁定到指定端口 |
方法:
方法簽名 | 方法說(shuō)明 |
---|---|
Socket accept() | 開(kāi)始監(jiān)聽(tīng)指定端口(創(chuàng)建時(shí)綁定的端口),有客戶端連接后,返回一個(gè)服務(wù)端Socket對(duì)象,并基于該Socket建立與客戶端的連接,否則阻塞等待 |
void close() | 關(guān)閉此套接字 |
2. Socket
Socket 是客戶端Socket,或服務(wù)端中接收到客戶端建立連接(accept方法)的請(qǐng)求后,返回的服務(wù)端Socket。
構(gòu)造方法:
方法簽名 | 方法說(shuō)明 |
---|---|
Socket(String host, int port) | 創(chuàng)建一個(gè)客戶端流套接字Socket,并與對(duì)應(yīng)IP的主機(jī)上,對(duì)應(yīng)端口的進(jìn)程建立連接 |
注意:這里面的 host 和 port 是要連接的服務(wù)器的 IP 地址和端口號(hào)。
方法:
方法簽名 | 方法說(shuō)明 |
---|---|
InetAddress getInetAddress() | 返回套接字所連接的地址 |
InputStream getInputStream() | 返回此套接字的輸入流 |
OutputStream getOutputStream() | 返回此套接字的輸出流 |
3. TCP的長(zhǎng)短連接
TCP發(fā)送數(shù)據(jù)時(shí),需要先建立連接,什么時(shí)候關(guān)閉連接就決定是短連接還是長(zhǎng)連接:
短連接: 每次接收到數(shù)據(jù)并返回響應(yīng)后,都關(guān)閉連接,即是短連接。也就是說(shuō),短連接只能一次收數(shù)據(jù)。
長(zhǎng)連接: 不關(guān)閉連接,一直保持連接狀態(tài),雙方不停的收發(fā)數(shù)據(jù),即是長(zhǎng)連接。也就是說(shuō),長(zhǎng)連接可以多次收發(fā)數(shù)據(jù)。
兩者區(qū)別如下:
- 建立連接、關(guān)閉連接的耗時(shí):短連接每次請(qǐng)求、響應(yīng)都需要建立連接,關(guān)閉連接;而長(zhǎng)連接只需要第一次建立連接,之后的請(qǐng)求、響應(yīng)都可以直接傳輸。相對(duì)來(lái)說(shuō)建立連接,關(guān)閉連接也是要耗時(shí)的,長(zhǎng)連接效率更高。
- 主動(dòng)發(fā)送請(qǐng)求不同:短連接一般是客戶端主動(dòng)向服務(wù)端發(fā)送請(qǐng)求;而長(zhǎng)連接可以是客戶端主動(dòng)發(fā)送請(qǐng)求,也可以是服務(wù)端主動(dòng)發(fā)。
- 兩者的使用場(chǎng)景有不同:短連接適用于客戶端請(qǐng)求頻率不高的場(chǎng)景,如瀏覽網(wǎng)頁(yè)等。長(zhǎng)連接適用于客戶端與服務(wù)端通信頻繁的場(chǎng)景,如聊天室,實(shí)時(shí)游戲等。
4. Socket 通信模型
5. 代碼示例:TCP 回顯服務(wù)器
服務(wù)器代碼:
class TcpEchoServer {
public ServerSocket serverSocket;//專門(mén)用來(lái)接受請(qǐng)求并建立鏈接
public Socket clientSocket;//專門(mén)用來(lái)處理請(qǐng)求
public TcpEchoServer(int port) throws IOException {
this.serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)!");
//也可以利用線程池
ExecutorService threadsPool= Executors.newCachedThreadPool();
while(true){
//接受請(qǐng)求
clientSocket=serverSocket.accept();
// //利用多線程才能讓服務(wù)器同時(shí)處理多個(gè)客戶端的請(qǐng)求
// Thread t=new Thread(()->{
// //建立鏈接并處理請(qǐng)求
// try {
// createConnection(clientSocket);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// });
// t.start();
//創(chuàng)建線程池相對(duì)于每次創(chuàng)建一個(gè)線程來(lái)說(shuō)效率更高一些
threadsPool.submit(()->{
try {
createConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
public void createConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d]建立鏈接成功\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//三個(gè)步驟
//1.讀取客戶端請(qǐng)求(根據(jù)打開(kāi)的文件流確定了讀取的是客戶端發(fā)來(lái)的請(qǐng)求)
//這里針對(duì)TCP的讀寫(xiě)和對(duì)于文件的讀寫(xiě)是一摸一樣的
//利用socket構(gòu)造文件流
try(InputStream inputStream=clientSocket.getInputStream()){//注意打開(kāi)的流
//直接利用scanner讀取(利用原生的InputStream也是可以的,但Scanner更方便)
Scanner scanner=new Scanner(inputStream);
try(OutputStream outputStream=clientSocket.getOutputStream()){//注意打開(kāi)的流
while(true){
if(!scanner.hasNext()){
System.out.printf("[%s:%d]斷開(kāi)鏈接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//讀取請(qǐng)求(TCP以字符流進(jìn)行傳輸)
// 讀到空白符/ 空格/換行才會(huì)停止
String request=scanner.next();
//2.根據(jù)請(qǐng)求計(jì)算響應(yīng)
String response=process(request);
//3.返回響應(yīng)(根據(jù)打開(kāi)的文件流決定了是往客戶端返回請(qǐng)求)
//為了方便用PrintWriter對(duì)OutputStream進(jìn)行包裹
PrintWriter printWriter=new PrintWriter(outputStream);
// 因?yàn)槭褂?next,讀到空白符/ 空格/換行才會(huì)停止,所以須使用 println
printWriter.println(response);
printWriter.flush();
System.out.printf("[%d][req:%s resp:%s]\n",clientSocket.getPort(),request,response);
}
}
}finally {
clientSocket.close();//記得及時(shí)關(guān)閉
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer=new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
客戶端代碼:
class TcpEchoClient {
public Socket client;
//TCP中客戶端構(gòu)造函數(shù)的ip和port指的是要鏈接的服務(wù)器的IP和port
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
this.client = new Socket(serverIp, serverPort);
}
public void start() throws IOException {
System.out.println("和服務(wù)器建立鏈接成功");
Scanner scanner = new Scanner(System.in);
//這里針對(duì)TCP的讀寫(xiě)和對(duì)于文件的讀寫(xiě)是一摸一樣的
//利用socket構(gòu)造文件流
try (InputStream inputStream = client.getInputStream()) {
try (OutputStream outputStream = client.getOutputStream()) {
//接收從控制臺(tái)輸入的字符串
while (true) {
System.out.println("->");
String request = scanner.next();
//構(gòu)造請(qǐng)求并發(fā)送請(qǐng)求(PrintWriter和Scanner對(duì)應(yīng))//注意文件流
PrintWriter printWriter = new PrintWriter(outputStream);
// 因?yàn)槭褂?next,讀到空白符/ 空格/換行才會(huì)停止,所以須使用 println
printWriter.println(request);
printWriter.flush();//如果不及時(shí)刷新,服務(wù)器可能不能及時(shí)接收到數(shù)據(jù)
//接收響應(yīng)
Scanner respScanner = new Scanner(inputStream);
String response = respScanner.next();
//顯示到控制臺(tái)上
System.out.printf("[req:%s resp:%s]\n", request, response);
}
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);
tcpEchoClient.start();
}
}
注意:當(dāng)然要先啟動(dòng)服務(wù)器再啟動(dòng)客戶端!文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-733310.html
好啦! 以上就是對(duì) TCP Socket編程的講解,希望能幫到你 !
評(píng)論區(qū)歡迎指正 !文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-733310.html
到了這里,關(guān)于【網(wǎng)絡(luò)編程】TCP Socket編程的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!