9、socket

“我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容“
TCP/IP只是一个协议栈,就像程序运行一样,必须要实现运行,同时还要提供对外的操作接口

网络从下往上分为
  物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,
应用层协议有很多,比如HTTP、FTP、TELNET等,也可以自己定义应用层协议。
  通过Socket,我们才能使用TCP/IP协议;实际上,Socket跟TCP/IP协议没有必然的联系。
  Socket编程接口在设计的时候,希望也能适应其他的网络协议。
  所以说,Socket的出现只是使得程序员方便地使用TCP/IP协议,是对TCP/IP协议的抽象,从而形成了一些基本的函数接口,比如create、listen、connect、accept、send、read和write等等。
  实际上,传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又是基于传输层的TCP协议的,而Socket本身不算是协议,就像上面所说,它只是提供了一个针对TCP或者UDP编程的接口。
  一、什么是TCP连接的三次握手
  第一次握手:客户端发送syn包(synchronous 同步的; sɪŋkrənəs, syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
  第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
  握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。
  理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
  服务器和客户端均可以主动发起断开TCP连接请求,断开过程需要经过“四次握手”,最终确定断开。
  二、利用Socket建立网络连接的步骤
  建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
  套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
  1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
  2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。
  为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  3、连接确认:当服务器端套接字监听到/接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。
  而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
  三、HTTP链接的特点
  HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。
  HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
  四、TCP和UDP的区别
  1、TCP是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性;
  而UDP不是面向连接的,UDP传送数据前并不与接收方建立连接,接收方也不发送确认信号,发送端不知道数据是否会正确接收,也不重发,所以说UDP是无连接的、不可靠的一种数据传输协议。
  2、由1描述的特点,使得UDP的开销更小、数据传输速率更高,因为不必进行收发数据的确认,所以UDP的实时性更好。
  知道了TCP和UDP的区别,就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了,但并不能说QQ的通信是不安全的,
  因为程序员可以手动对UDP的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证。


1、服务器端获取客服端传来的数据:
直接在eclipse中新建一个Server类:

import java.io.*;
import java.net.*;
public class Server {
public static void main(String args[]) throws IOException {
int port = 8899;
//定义一个ServerSocket监听在端口8899上
ServerSocket server = new ServerSocket(port);
//server尝试接收客户端Socket的连接请求,server的accept方法是阻塞式的
Socket socket = server.accept();
//读取客户端发过来的信息
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
while ((len=reader.read(chars)) != -1) {
sb.append(new String(chars, 0, len));
}
System.out.println("from client: " + sb);
reader.close();
socket.close();
server.close();
}
}

阻塞式:当服务器socket接收到数据才往后执行,没有收到数据就暂停在当前程序位置等待数据。
可以先执行Server的主方法,执行后,查看端口:cmd-->netstat -ano 就会发现8899端口处于监听的状态。
根据进程pid查看该端口被那个应用占用:
C:\Users\Administrator>tasklist|findstr 4844
javaw.exe 4844 Console 1 14,992 K
说明Server类的主方法并没有执行完毕。
Client类:

import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]) throws Exception {
String host = "127.0.0.1"; //要连接的服务端IP地址
int port = 8899; //要连接的服务端对应的监听端口
//与服务端建立连接
Socket client = new Socket(host, port);
//建立连接后就可以往服务端写数据了
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server.");
writer.flush();//写完后要记得flush
writer.close();
client.close();
}
}

执行Client的主方法,控制台上打印:from client: Hello Server.
这时再去查看端口8899,发现端口已经被关闭,Server的主方法执行完毕。
若先执行Client主方法会是怎样?会出现Connection refused: connect
若将Client类中writer.flush();删掉会是怎样?此时还是能读取到from client: Hello Server.

2、客服端、服务器同时读写数据:
每次数据写入时 添加结束标记:"eof";发现端口8899开启过多可以先关闭eclipse或javaw.exe,确保每次运行Server之前8899端口是关闭的。
Server:

import java.io.*;
import java.net.*;
public class Server {
public static void main(String args[]) throws IOException {
int port = 8899;
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept();
Reader reader = new InputStreamReader(socket.getInputStream());
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//读完后写一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client32.eof3232"); //出现eof表示输入结束
writer.flush();
writer.close();
reader.close();
socket.close();
server.close();
}
}

Client类:

import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]) throws Exception {
String host = "127.0.0.1"; //要连接的服务端IP地址
int port = 8899; //要连接的服务端对应的监听端口
Socket client = new Socket(host, port);
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server");
writer.write("eof");
writer.flush();
Reader reader = new InputStreamReader(client.getInputStream());
char chars[] = new char[64];
int len;
StringBuffer sb = new StringBuffer();
String temp;
int index;
while ((len=reader.read(chars)) != -1) {
temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {
sb.append(temp.substring(0, index));
break;
}
sb.append(new String(chars, 0, len));
}
System.out.println("from server: " + sb);
reader.close();
writer.close();
client.close();
}
}

还是先执行Server主方法再执行Client主方法:
点击Console按钮切换Server/Client的打印信息:
from server: Hello Client32.
from client: Hello Server

3、多个客户端链接同一个服务器端
这才是实际需要的,还是以"eof"为结束标记;这里没有关闭端口。
Server:每次ServerSocket接收到一个新的Socket连接请求后新建一个线程来跟当前Socket进行通信,实现并发处理与客户端Socket进行通信的情况。由于现在的Server是异步处理数据的,不必一次性将客户端传来的数据读完,可以使用BufferedReader这个类,每次读取一行数据。

import java.io.*;
import java.net.*;
public class Server {
@SuppressWarnings("resource")
public static void main(String args[]) throws IOException {
int port = 8899;
ServerSocket server = new ServerSocket(port);
while (true) {
Socket socket = server.accept();
new Thread(new Task(socket)).start(); //启动Runnable接口下类的run()方法;
}
}

/**
* 用来处理Socket请求的
*/
static class Task implements Runnable {

private Socket socket;

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

public void run() {
try {
handleSocket();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 跟客户端Socket进行通信
* @throws Exception
*/
private void handleSocket() throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuilder sb = new StringBuilder();
String temp;
int index;
while ((temp=br.readLine()) != null) {
if ((index = temp.indexOf("eof")) != -1) {//遇到eof时就结束接收
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from client: " + sb);
//读完后写一句
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.write("eof");
writer.flush();
writer.close();
br.close();
socket.close();
}
}
}

可以用线程实例多个Client对象模拟并发访问Server,这里就不实现了(也可以创建2个Client类简单模拟下)。

import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]) throws Exception {
String host = "127.0.0.1"; //要连接的服务端IP地址
int port = 8899; //要连接的服务端对应的监听端口
Socket client = new Socket(host, port);
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server first\n");
writer.write("Hello Server second\n");
writer.write("eof\n");
writer.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
StringBuffer sb = new StringBuffer();
String temp;
int index;
while ((temp=br.readLine()) != null) {
if ((index = temp.indexOf("eof")) >0) {
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
System.out.println("from server: " + sb);
writer.close();
br.close();
client.close();
}
}

设置编码:BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
Writer writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");

设置阻塞超时中断连接(一般设置在客户端),Socket提供setSoTimeout()方法来设置接收数据的超时时间,单位是毫秒,抛出SocketTimeoutException。
在server中添加:
Thread.sleep(5000);
// writer.flush();
Client:

import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]) throws Exception {
String host = "127.0.0.1"; //要连接的服务端IP地址
int port = 8899; //要连接的服务端对应的监听端口
Socket client = new Socket(host, port);
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server 我的\n");
writer.write("Hello Server second\n");
writer.write("eof\n");
writer.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
client.setSoTimeout(2000);
StringBuffer sb = new StringBuffer();
try{
String temp;
int index;
while ((temp=br.readLine()) != null) {
if ((index = temp.indexOf("eof")) >0) {
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
}catch(SocketTimeoutException e){
System.out.println("数据读取超时。");
}
System.out.println("from server: " + sb);
writer.close();
br.close();
client.close();
}
}

客服端就会打印超时;
可能出现的异常 Address already in use: JVM_Bind
最好用netstat -ano查看后、决定是否关闭相应进程或修改端口。
一般解决办法:
1、修改程序端口
2、重启eclipse或javaw.exe

前节讲到是字符串的传递,这里是服务器与客户端之间对象的传递:
1、创建一个User对象要实现序列化接口:

public class User implements java.io.Serializable{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}

2、编写Server类:

import java.io.*;
import java.net.*;
public class Server {
public static void main(String args[]) throws IOException, ClassNotFoundException {
int port = 8899;
//定义一个ServerSocket监听在端口8899上
ServerSocket server = new ServerSocket(port);
//server尝试接收客户端Socket的连接请求,server的accept方法是阻塞式的
Socket socket = server.accept();
ObjectInputStream ois=new ObjectInputStream(socket.getInputStream());
User u=(User)ois.readObject();
System.out.println("from client: " + u.getName() +"年龄:"+ u.getAge());
ois.close();
socket.close();
server.close();
}
}

3、编写客户端:

import java.io.*;
import java.net.*;
public class Client{
public static void main(String args[]) throws Exception {
String host = "127.0.0.1"; //要连接的服务端IP地址
int port = 8899; //要连接的服务端对应的监听端口
Socket client = new Socket(host, port);
User u =new User();
u.setAge(14);
u.setName("skx");
ObjectOutputStream oos =new ObjectOutputStream(client.getOutputStream());
oos.writeObject(u);
oos.close();
client.close();
}
}

运行得到:
from client: skx年龄:14

qq聊天器模拟
依照韩顺平 山寨qq
我这里在公司里实现是可以的,无数据库,登陆账号1-20,密码123456,不要注册和添加好友,以免造成些麻烦
里面的ip地址是192.168.0.65这是我办公室电脑的局域网地址,下载之后自行更改ip地址,若单机可以更改为127.0.0.1
可以将Qclient打包成jar文件,添加bat命令,发送给同事,他们只需要点击bat就可以实现联机。
jar -qqc.jar
下载地址及提取码
链接: http://pan.baidu.com/s/1bndDR8j 密码: ctxb
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值