20 网络编程
20.1 网络编程介绍
网络模型: Osi 参数模型TCP/IP 参考模型
网络通讯要素
IP地址
端口号
传输协议
通讯规则:
1.找到对方IP
2.数据要发送到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用
数字进行标识。为了方便称呼这数字,叫做端口。逻辑端口
3.定义通讯规则。这通讯规则称为协议 国际定义了 通用协议 TCP/IP
20.2 网络参考模型
网际层 IP 协议传输层 TCP/UDP 协议
应用层 http /ftp 都属于应用层协议
20.3 网络通讯要素
1.IP地址:InetAddress网络中设备的标识
不易记忆,可用主机名
本地回环地址:127.0.0.1 主机名:localhost
2.端口号
用于标识进程的逻辑地址,不同进程的标识
有效端口:0~65535,其中0~1024系统使用或保留端口。
3.传输协议
通讯的规则
常见协议:TCP,UDP
20.4 TCP和UDP协议详解
20.4.1 UDP
网络视频,桌面共享 一般用的都是不需要保证数据的完整性 UDP协议1)将数据及源和目的封装成数据包中,不需要建立连接
2)每个数据报的大小在限制在64k内
3)因无连接,是不可靠协议
4)不需要建立连接,速度快
20.4.2 TCP
下载 数据显示 需要保证数据的完整性的都是用的TCP协议1)建立连接,形成传输数据的通道。
2)在连接中进行大数据量传输
3)通过三次握手完成连接,是可靠协议
4)必须建立连接,效率会稍低
20.5 Socket编程
也称为套接字Socket就是为网络服务提供的一种机制。
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
20.5.1 UDP传输
传输步骤:1)DatagramSocket与DatagramPacket
udp需要将信息封装成包再发送出去 类-- DatagramPacket
DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。
DatagramSocket(int port, InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址。
DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
2)建立发送端,接收端。
3)建立数据包。
4)调用Socket的发送接收方法。
5)关闭Socket。
简单示例:
public static void main(String[] args) throws Exception {
byte[] b ="fuck bitch the socket".getBytes();
byte[] bs =new byte[1024];
//发送socket
DatagramSocket socket = new DatagramSocket(9999,InetAddress.getLocalHost());
//接收socket2
DatagramSocket socket2 = new DatagramSocket(10000,InetAddress.getLocalHost());
//发送的数据包
DatagramPacket dp = new DatagramPacket(b,b.length);
//接收数据包
DatagramPacket dp2 = new DatagramPacket(bs,bs.length);
dp.setAddress( InetAddress.getLocalHost());
dp.setPort(10000);
socket.send(dp);
socket2.receive(dp2);
//获取接收的数据
System.out.println(new String(dp2.getData(),0,b.length));
socket.close();
socket2.close();
}
UDP 聊天程序
要求 1.通过键盘录入获取要发送的信息。2.将发送和接收分别封装到两个线程中。
package socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class SocketIm {
String username; //用户名
DatagramSocket ds;
public SocketIm(String username,int port,InetAddress laddr) throws SocketException{
ds = new DatagramSocket(port, laddr);
this.username = username;
}
/**
* 用户登录 并且开始聊天
* @param port 目标端口号
* @param laddr 目标IP地址
* @throws SocketException
*/
public void shangxian(int port,InetAddress laddr) throws SocketException{
new Send(this,port,laddr).start();
new Reseve(this).start();
}
public String getUsername() {
return username;
}
public DatagramSocket getDs() {
return ds;
}
public static void main(String[] args) {
try {
new SocketIm("li", 9999, InetAddress.getLocalHost()).shangxian(10000, InetAddress.getLocalHost());
new SocketIm("小米", 10000, InetAddress.getLocalHost()).shangxian(9999, InetAddress.getLocalHost());
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Send extends Thread{
SocketIm ds;
int port;
InetAddress laddr;
/**
* 发送
* @param ds
*/
public Send(SocketIm ds,int port,InetAddress laddr){
this.ds = ds;
this.port = port;
this.laddr = laddr;
}
@Override
public void run() {
BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
DatagramPacket dp = null ;
while(true){
byte[] b = null;
try {
b = (ds.getUsername()+" 对你说 : "+br.readLine()).getBytes();
dp = new DatagramPacket(b,b.length,laddr,port);
ds.getDs().send(dp);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Reseve extends Thread{
SocketIm ds;
/**
* 发送
* @param ds
*/
public Reseve(SocketIm ds){
this.ds = ds;
}
@Override
public void run() {
byte[] b = new byte[1024*1024];
DatagramPacket dp2 = new DatagramPacket(b,b.length);
while(true){
try {
ds.getDs().receive(dp2);
System.out.println(new String(dp2.getData(),0,dp2.getLength()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
20.6.1 TCP详解
1.连接步骤:1)Socket和ServerSocket
Socket(InetAddress address, int port)
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(String host, int port)
创建一个流套接字并将其连接到指定主机上的指定端口号。
InputStream getInputStream() 返回此套接字的输入流。
OutputStream getOutputStream() 返回此套接字的输出流。
2)建立客户端和服务器端
3)建立连接后,通过Socket中的IO流进行数据的传输
4)关闭socket
5)同样,客户端与服务器端是两个独立的应用程序。
2.客户端的基本思路:
客户端需要明确服务器的ip地址以及端口,这样才可以去试着建立连接,如果连接失败,会出现异常。
连接成功,说明客户端与服务端建立了通道,那么通过IO流就可以进行数据的传输,而Socket对象已经
提供了输入流和输出流对象,通过getInputStream(),getOutputStream()获取即可。
与服务端通讯结束后,关闭Socket。
3.服务端的基本思路:
服务端需要明确它要处理的数据是从哪个端口进入的。
当有客户端访问时,要明确是哪个客户端,可通过accept()获取已连接的客户端对象,并通过该对象与客户端通过IO流进行数据传输。
当该客户端访问结束,关闭该客户端。
注意事项:
记得标记该socket结束
在读取文件的时候 有两种方式可以结束读取文件
1.自己写自定义的结束约定
2.使用socket本身自带的结束约定方法
void shutdownInput() 此套接字的输入流置于“流的末尾”。
void shutdownOutput() 禁用此套接字的输出流。
示例:简易聊天室
//============客户端实现===================package socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客户端
* @author 李昂志
*/
public class ClinetTest {
String name ;
public Socket s;
public ClinetTest(String name){
this.name = name;
}
public String getName(){
return name;
}
public void startClient(){
try {
s = new Socket("localhost", 10000);
new Thread(new SendMsg(this)).start();
new Thread(new ReserveMsg(this)).start();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
new ClinetTest("Big").startClient();
}
}
/**
* 客户端发送信息
* @author 李昂志
*/
class SendMsg implements Runnable{
ClinetTest sk;
Socket s;
public SendMsg(ClinetTest clinetTest){
this.sk = clinetTest;
s = sk.s;
}
@Override
public void run() {
// TODO Auto-generated method stub
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = null;
try {
out = new PrintWriter(s.getOutputStream(),true);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
while(true){
try {
out.println(sk.name+" 说:"+br.readLine());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* 客户端接收信息
* @author 李昂志
*/
class ReserveMsg implements Runnable{
ClinetTest sk;
Socket s;
public ReserveMsg(ClinetTest clinetTest){
this.sk = clinetTest ;
s = sk.s;
}
@Override
public void run() {
BufferedReader in = null ;
try {
in = new BufferedReader(new InputStreamReader(s.getInputStream()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
while(true){
try {
System.out.println(in.readLine());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//============服务端实现===================
package socket;
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.util.ArrayList;
import java.util.List;
/**
* 服务端
* @author 李昂志
*/
public class ServerTest {
public static List<Socket> ls = new ArrayList<Socket>();
public ServerTest(){
}
public void startServer(){
ServerSocket ss = null;
Socket s = null;
try {
ss = new ServerSocket(10000);
System.out.println("服务器已经启动.....");
while(true){
try {
s = ss.accept();
System.out.println("监听到一个客户端开启");
ls.add(s);
new Thread(new SrevMsg(s)).start();
} catch (Exception e) {
// TODO Auto-generated catch block
ls.remove(s);
s.close();
}
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public static void main(String[] args) {
new ServerTest().startServer();
}
}
/**
* 接收到一个用户的信息则马上转发给所有在线的用户
* @author 李昂志
*/
class SrevMsg implements Runnable{
String msg ;
Socket s ;
public SrevMsg(Socket s){
this.s = s;
}
@Override
public void run() {
BufferedReader br = null;
try {
br= new BufferedReader(new InputStreamReader(s.getInputStream()));
while(true){
if(!s.isClosed()){
msg = br.readLine();
sendMsg(msg);
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
ServerTest.ls.remove(s);
sendMsg(s.getInetAddress().getHostName()+" 用户退出聊天室!");
try {
if(s!= null){
s.close();
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
public void sendMsg(String msg){
PrintWriter out = null;
for(Socket ss : ServerTest.ls ){
if(s == ss) continue;
if(ss.isConnected()){
try {
out = new PrintWriter(ss.getOutputStream(),true);
out.println(msg);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
ServerTest.ls.remove(ss);
}
}
}
}
20.6.2 Tcp传输问题
客户端连接上服务端,两端都在等待,没有任何数据传输。通过例程分析:
因为read方法或者readLine方法是阻塞式。
解决办法:
自定义结束标记
使用shutdownInput,shutdownOutput方法。