網絡編程技術是互聯網技術中的主流編程技術之一,懂的一些基本的操作是非常必要的。這章主要講解網絡編程,UDP和Socket編程,以及使用Socket做一個簡單的聊天軟件。
全部代碼下載:鏈接
1.網絡編程簡要概述:
網絡編程實質實質就是兩個(或多個)設備(例如計算機)之間的數據傳輸。而要實現兩台計算機通過互聯網連接進行數據傳輸,必輸要滿足計算機網絡的5層協議(物理層,數據鏈路層,網絡層,運輸層,應用層);當然有划分可能不同,但現在大家比較容易接受理解的是五層模型。而其中前三層物理層,數據鏈路層以及網絡層,作為java程序員暫時是不能直接對他進行控制的。而本章講的網絡編程是處在運輸層和應用層中。對與計算機網絡,可以參看我的博客互聯網協議入門
- 運輸層主要有兩種協議TCP和UDP,TCP通過握手協議進行可靠的連接,UDP則是不可靠連接。
- 應用層主要就是應用層直接和應用程序接口並提供常見的網絡應用服務,主要協議有HTTP,FTP,DNS等。
- IP地址:用與標記一台計算機的身份證,此去需要注意的是網絡地址轉換技術NAT的存在,我們的個人計算機基本上都是專用網絡IP(192.168..),這個IP只能本地電腦訪問。只有當我們有公網IP后,才能使自己的電腦被世界所有連接在互聯網中的電腦訪問到。
- 服務端口:計算機通過不同的服務端口來區分不同網絡服務,就像通過進程號區分不同的進程一樣;常見服務的端口,HTTP(80),FTP(21).
- URL:統一資源定位符。只能定位互聯網資源。
URL基本格式:
protocol://hostname:port/resourcename#anchor
protocol:使用的協議,可以是http,ftp,news,telnet等
hostname:主機名
port:端口號,可選
resourcename:資源名,主機上能訪問到的目錄或文件
anchor:標記,可選,指定文件中的有特定標記的位置
如:
http://localhost:8080/HttpSer/index.html。
下面主要通過UDP和socket編程來講解網絡編程;
2. UDP編程介紹:
- 簡介:
UDP(User Datagram Protocol),中文意思是用戶數據報協議,方式類似於發短信息,是一種物美價廉的通訊方式,使用該種方式無需建立專用的虛擬連接,由於無需建立專用的連接,所以對於服務器的壓力要比TCP小很多。是一種盡可能可靠傳輸的協議。主要用於視頻電話等對信息准確傳輸性不高追求速度的應用程序。 - 主要類的講解:
DatagramSocket:
DatagramSocket類實現“網絡連接”,包括客戶端網絡連接和服務器端網絡連接。雖然UDP方式的網絡通訊不需要建立專用的網絡連接,但是畢竟還是需要發送和接收數據,DatagramSocket實現的就是發送數據時的發射器,以及接收數據時的監聽器的角色
DatagramPacket:
DatagramPacket類實現對於網絡中傳輸的數據封裝,也就是說,該類的對象代表網絡中交換的數據。在UDP方式的網絡編程中,無論是需要發送的數據還是需要接收的數據,都必須被處理成DatagramPacket類型的對象,該對象中包含發送到的地址、發送到的端口號以及發送的內容等。 - 一個簡單的udp通信:
客戶端:
DatagramSocket ds = null;
//定義一個UDP來發送數據
ds = new DatagramSocket();
//假設發送的數據是個字符串
String hello = "hello world";
//定義一個UDP的數據發送包來發送數據,inetSocketAddress表示要接收的地址
DatagramPacket dp = new DatagramPacket(hello.getBytes()
,hello.getBytes().length,new InetSocketAddress("127.0.0.1", 9999));
for(int i=0;i<10;i++) {
//數據發送
ds.send(dp);
//線程睡眠1s
Thread.sleep(1000);
}
服務端:
DatagramSocket ds = null;
//UDP接受端連接
ds = new DatagramSocket(9999);
//定義將UDP的數據包接收到什么地方
byte[] buf = new byte[1024];
//定義UDP的數據接收包
DatagramPacket dp = new DatagramPacket(buf, buf.length);
while(true) {
//接收數據包
ds.receive(dp);
//將數據轉換輸出:
String str = new String(dp.getData(),0,dp.getLength());
System.out.println(str);
}
3.socket編程介紹:
- socket簡介:
Socket是TCP/IP協議的一個十分流行的編程界面,一個Socket由一個IP地址和一個端口號唯一確定。
而TCP是Tranfer Control Protocol的 簡稱,是一種面向連接的保證可靠傳輸的協議。通過TCP協議傳輸,得到的是一個順序的無差錯的數據流。發送方和接收方的成對的兩個socket之間必須建立連接,以便在TCP協議的基礎上進行通信,當一個socket(通常都是server socket)等待建立連接時,另一個socket可以要求進行連接,一旦這兩個socket連接起來,它們就可以進行雙向數據傳輸,雙方都可以進行發送 或接收操作。 - 主要的類介紹:
1.建立服務器類:ServerSocket
可以用服務器需要使用的端口號作為參數來創建服務器對象:
ServerSocket serverSocket = new ServerSocket(8888)
這條語句創建了一個服務器對象,這個服務器使用8888號端口。當一個客戶端程序建立一個Socket連接,所連接的端口號為8888時,服務器對象server便響應這個連接,並且server.accept()方法會創建一個Socket對象。服務器端便可以利用這個Socket對象與客戶進行通訊。
//進行監聽,當客戶端沒數據發送過來時,停在這里;
Socket s=serverSocket.accept();
2.通信類:Socket
服務器端和客戶端可以通過Socket類進行通信:
客戶端建立socket類過程:
//建立socket客戶端:第一個參數:ip地址;第二個參數:發送的端口,假如沒有服務器,會停在這里,然后扔出異常;
Socket socket=new Socket("192.168.1.105", 8888);
通過socket類可以獲得輸入輸出流進行通信:
//獲得socket的輸出流
PrintWriter out=new PrintWriter(socket.getOutputStream());
//獲得socket的輸入流
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
- 一個簡單的Socket通信:
客戶端:
public static void main(String[] args) {
Socket socket=null;
PrintWriter out=null;
BufferedReader in=null;
try {
//建立socket客戶端:第一個參數:ip地址;第二個參數:發送的端口,假如沒有服務器,會停在這里,然后扔出異常;
socket=new Socket("192.168.1.105", 8888);
//獲得本機分配的當地socket端口
System.out.println("當地端口:"+socket.getLocalPort());
//獲得本機分配的當地socket端口
System.out.println("遠程端口:"+socket.getPort());
//獲得socketAddress
System.out.println("遠程adress:"+socket.getRemoteSocketAddress());
System.out.println("本地adress:"+socket.getLocalSocketAddress());
//獲得inetAddress
System.out.println("遠程inetAddress:"+socket.getInetAddress());
System.out.println("本地inetAddress:"+socket.getLocalAddress());
//獲得socket的輸出流
out=new PrintWriter(socket.getOutputStream());
//獲得socket的輸入流
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
//發送數據
out.println("peace");
//刷新才會立即發送
out.flush();
//接收數據
String str=null;
//此去會一直等待服務端發送一個數據;
str=in.readLine();
System.out.println("收到:"+str);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//關閉連接
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
服務端:
public static void main(String[] args) {
ServerSocket serverSocket=null;
PrintWriter out=null;
BufferedReader in=null;
Socket s=null;
int i=0;
try {
//服務器的創建:創建相應的serversocket
serverSocket=new ServerSocket(8888);
while(true){
try {
//進行監聽,當客戶端沒數據發送過來時,停在這里;
s=serverSocket.accept();
//獲得本機分配的當地socket端口
System.out.println("當地端口:"+s.getLocalPort());
//獲得本機分配的當地socket端口
System.out.println("遠程端口:"+s.getPort());
//獲得socketAddress
System.out.println("遠程adress:"+s.getRemoteSocketAddress());
System.out.println("本地adress:"+s.getLocalSocketAddress());
//獲得inetAddress
System.out.println("遠程inetAddress:"+s.getInetAddress());
System.out.println("本地inetAddress:"+s.getLocalAddress());
//獲得socket的輸出流,關閉socket時,會自動關閉socket的輸出輸入留
out=new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
//獲得socket的輸出流,關閉socket時,會自動關閉socket的輸出輸入留
in=new BufferedReader(new InputStreamReader(s.getInputStream()));
//將客戶端發過來的數據輸出:
String str=null;
str=in.readLine();
System.out.println("收到"+str);
out.println("hello"+i++);
out.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(s!=null){
s.close();
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
4.利用socket建立簡單聊天程序:
該聊天進程可以實現指定好友聊天,和群聊。只是簡單的演示。
- 客戶端
線程數:2個,一個主線程用於發送消息,一個線程用於接收服務器發送過來的消息
單聊實現:通過single標志進行判斷。
退出實現:控制抬輸入:quit就會提示退出
單聊退出實現:當進入單聊后,控制台輸入q就退出單聊 - 服務端:
線程數:由客戶端決定,當客戶端鍵入名字之后就啟動一個線程用於處理客戶端的各種請求,以及發送數據。將所有線程放入map集合,鍵為用戶名,值為線程對象;
單聊實現:當收到客戶端的singl標志后使sin為true實現單聊,同時獲得指定用戶的線程進行發送數據。
退出實現:當接收到客戶端發送的quit時,向客戶端發送disconnect斷開連接,關閉線程
單聊退出:將單聊標志置為false就行; - 客戶端代碼如下:
package com.rlovep.clinet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
/**
*
* @ClassName: TalkClinet
* @Description: 聊天客戶端:
* @author peace w_peace@163.com
* @date 15 Oct 2015 7:21:22 pm
*
*/
public class TalkClinet {
// 獲得控制台輸入
private BufferedReader sysin = null;
// socket
private Socket s = null;
private BufferedReader in = null;
private PrintWriter out = null;
// 線程停止flag
private boolean flag = true;
//單聊標志
private final static String single="single";
public static void main(String[] args) {
// 啟動客戶端:
new TalkClinet().start();
}
/**
*
* @Title: start
* @Description: 主要作用:啟動發送和接收任務
* @return:void
* @throws
* @author peace w_peace@163.com
*/
public void start(){
try {
// 獲得系統輸入:
sysin = new BufferedReader(new InputStreamReader(System.in, "utf-8"));
// 設置名字:
System.out.println("請輸入用戶名:");
String name = sysin.readLine();
// 建立客戶端socket
s = new Socket("127.0.0.1", 8888);
// 獲得socket輸出out
out = new PrintWriter(s.getOutputStream(), true);
out.println(name);
// 獲得socket輸入 in
in = new BufferedReader(new InputStreamReader(s.getInputStream()));
// 建立接收線程;原因獲取系統輸入會阻塞主線程
new Thread(new ClinetThread()).start();
// 發送消息:
String str = null;
while (flag && ((str = sysin.readLine()) != null)) {
//判斷是否為單聊標志。如果不是單聊,就群發消息
if(single.equalsIgnoreCase(str)){
System.out.println("請輸入想跟誰聊 :");
//獲取系統輸入:
if(flag && ((str = sysin.readLine()) != null))
{
//發送單聊標志
out.println(single) ;
}
}
//向服務端發送內容:
out.println(str);
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
//關閉資源
if(sysin!=null){
try {
sysin.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(s!=null){
try {
s.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
private class ClinetThread implements Runnable{
/**
*
* @Title: recive
* @Description: 接收消息,當消息為disconnect時退出,此去需要在按下一次回車用來終止系統輸入;
* @return:void
* @throws
* @author peace w_peace@163.com
*/
private void recive(){
try {
//接收服務端消息
String str=in.readLine();
if(str!=null){
//如果是結束聊天,就退出線程
if("disconnect".equals(str)){
stop();
System.out.println("回車退出:");
}
else
{
//否則顯示消息
System.out.println(str);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//線程停止方法
public void stop()
{
flag=false;
}
//線程主要任務
@Override
public void run() {
while(flag){
//接收消息函數調用
recive();
}
}
}
}
4.服務端代碼如下:
package com.rlovep.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
public class TalkServer {
//保存聊天線程:
private Map<String, ServerThread> map = new HashMap<String, ServerThread>();
public static void main(String[] args) {
//啟動服務器
new TalkServer().start();
}
/**
*
* @Title: start
* @Description: 為每一個客戶端創建一個獨立線程
* @return:void
* @throws
* @author peace w_peace@163.com
*/
public void start(){
//服務器servesocket
ServerSocket serverSocket=null;
//服務器socket
Socket socket=null;
try {
//服務器創建
serverSocket=new ServerSocket(8888);
//死循環,用於接收更多的客戶端
while(true){
//聊天線程創建
socket=serverSocket.accept();
//為每一個客戶端建立一個服務線程。
ServerThread st=new ServerThread(socket);
new Thread(st).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
//釋放資源
try {
if(serverSocket!=null){
serverSocket.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
*
* @ClassName: ServerThread
* @Description: 聊天線程
* @author peace w_peace@163.com
* @date 15 Oct 2015 8:31:44 pm
*
*/
private class ServerThread implements Runnable{
//相應的socket
private Socket socket=null;
private BufferedReader in=null;
private PrintWriter out=null;
//每個聊天的名字
private String name=null;
//聊天頭
private String meshead=null;
//停止線程標志位
private boolean flag=true;
//單聊標志位:
private boolean sin=false;
/**
*
* <p>Title:線程構造器 </p>
* <p>Description:用來創建線程,以及初始化,和將線程加入map </p>
* @param socket
* @throws IOException
*/
public ServerThread(Socket socket) throws IOException {
//獲得socket
this.socket=socket;
//獲得輸出和輸入
out=new PrintWriter(socket.getOutputStream(),true);
//獲得用戶名字
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
name=in.readLine();
System.out.println(name+"上線了");
//制作聊天頭
meshead=name+"["+socket.getInetAddress().getHostAddress()+":"+socket.getPort()+"]";
//將線程加入map:key為名字;value為線程
map.put(name, this);
//提醒所有用戶
send(meshead+"上線了");
}
/**
*
* @Title: send
* @Description: 發送消息(群發)
* @param msg
* @return:void
* @throws
* @author peace w_peace@163.com
*/
public void send(String msg){
//迭代群發
for(ServerThread thread:map.values()){
thread.out.println(msg);
}
}
/**
*
* @Title: Receiver
* @Description: 接收消息,並轉發
* @throws IOException
* @return:void
* @author peace w_peace@163.com
*/
public void Receiver() throws IOException{
String str=null;
ServerThread qq=null;
//接收消息
while((str=in.readLine())!=null){
//如果消息為quit則退出
if("quit".equalsIgnoreCase(str)){
stop();
//給客戶端發送關閉鏈接命令
out.println("disconnect");
break;
}
//判斷是否為單聊
if(sin==false)
{
if(str.equals("single")){
//如果為單聊就獲得想要跟誰聊
if((str=in.readLine())!=null){
//獲得另外一個客戶端線程
qq=map.get(str);
if(qq!=null)
sin=true;
}
}
else
{
//轉發消息 群發
send(meshead+str);
}
}
//如果是單聊就給對應的客戶端發送消息;
else{
//如果為q則退出單聊
if(str.equals("q")){
sin=false;
qq.out.println(name+"對話結束");
}
//否則發送消息
else
qq.out.println(name+"對你說:"+str);
}
}
}
/**
*
* @Title: stop
* @Description: 停止函數
* @return:void
* @throws
* @author peace w_peace@163.com
*/
public void stop(){
flag=false;
//下線移去碼map;
map.remove(name);
send(meshead+"已經下線了");
}
/**
* run方法
*/
@Override
public void run() {
try {
while(flag){
//不停接收消息
Receiver();
}
}
catch(SocketException e){
stop();//客戶端直接關閉引發的錯誤;
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
//釋放資源
try {
if(socket!=null){
socket.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
來自一條小鯊魚wpeace(rlovep.com)