Socket 一种独立于协议的网络编程接口,所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。
Socket主要类型 流套接字(SOCK_STREAM) 流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。 数据报套接字(SOCK_DGRAM) 数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP( User DatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理 。 原始套接字(SOCK_RAW) 原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接。
Socket主要流程 要通过互联网进行通信,至少需要一对套接字,其中一个运行于客户端,我们称之为 Client Socket,另一个运行于服务器端,我们称之为 Server Socket。
根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤: (1)服务器监听。 (2)客户端请求。 (3)连接确认 。
1.服务器监听 所谓服务器监听,是指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
2.客户端请求 所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端接字提出连接请求。
3.连接确认 所谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,就会响应客户端套接字的请求,建立一个新的线程,并把服务器端套接字的描述发送给客户端。一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,接收其他客户端套接字的连接请求。
Socket主要特点 根据套接字的不同类型,可以将套接字调用分为面向连接服务和无连接服务。
面向连接服务的主要特点如下: (1)数据传输过程必须经过建立连接、维护连接和释放连接3个阶段 ; (2)在传输过程中,各分组不需要携带目的主机的地址; (3)可靠性好,但由于协议复杂,通信效率不高 。
面向无连接服务的主要特点如下: (1)不需要连接的各个阶段 ; (2)每个分组都携带完整的目的主机地址,在系统中独立传送 ; (3)由于没有顺序控制,所以接收方的分组可能出现乱序、重复和丢失现象 ; (4)通信效率高,但可靠性不能确保。
Socket的引入 为了更方便地开发网络应用程序,美国伯克利大学在UNIX上推出了一种应用程序访问通信协议的操作系统调用接字(Socket)。 Socket的出现,使得程序员可以很方便地访问 TCPIP,从而开发各种网络应用程序。后来套接字被引进到 Windows等操作系统,成为开发网络应用程序的有效工具。 套接字存在于通信区域,通信区域也被称为地址族,主要用于将通过套接字通信的进程的公有特性综合在一起。套接字通常只与同一区域的套接字交换数据。Windows Socket只支持一个通信区域——AF_INET国际网区域,使用网际协议族通信的进程使用该域。
Http与Socket的区别 在以前我们实现数据交换已经有了HTTP协议,为什么还要学习Socket? 回顾当输出 www.baidu.com 的时候浏览器执行了那些操作?
http通信的特点: 连接属于非持久性连接:TCP的三次握手 客户端只能访问服务端,服务端无法访问客户端,属于单项通信 TCP三次握手: TCP三次握手过程中不传递数据,只为同步连接双方的序列号和确认号传递数据,在握手后服务端和客户端才开始传输数据,在理想状态下,TCP连接一旦建立,在通信的双方中任何一方主动断开连接之前TCP连接会一直保持下去。
Socket通信特点: 1、持久性连接 2、双向通信,客户端能访问服务端,服务端也能访问客户端
Socket是对TCP/IP协议的封装,Socket只是一个接口而不是一个协议,通过Socket我们才能使用TCP/IP/UDP协议。
Socket原理
开始写一个简单的Socket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 var net = require("net" )var server = net.createServer();server.on("connection" ,function (client ){ client.on("data" ,function (data ){ console .log ("客户端发来:" +data) client.write("测试" ); }) client.on('close' , function (err ) { console .log (client.remotePort + '退出了聊天室' ); } }) server.listen(3000 , function ( ) { console .log ('Starting Socket server...' ); console .log ('http://127.0.0.1:3000' ); console .log ('' ); }) server.on('error' , function ( ) { console .log ('Server error' ); }); client.js var net= require("net" )var socket = net.createConnection({ host : '127.0.0.1' , port : 3000 }) socket.on('connect' , function ( ) { process.stdin.on('data' , function (msg ) { socket.write(msg.toString(); }); } socket.on("data" ,function (data ){ console .log ("服务器发来:" +data) })
这是一个简单实例,也很好理解。
编写Chat-Cli Chat-cli就是在命令行上聊天都一种方式,为什么要在命令行上聊天呢?因为这样的程序占用较小,可以边敲命令边聊天,这看起来是不是很不错?接下来我们将深入的编写Chat-cli,我将我编写的命名为NChat-Cli。 实现原理归根结底就是将所有加入的用户存在一个数组里面。 那放数组里面怎么知道那个是谁呢?我们可以用内置的一个函数Socket.remotePort获取对应的id,可以将名字加id结合起来找到对应用户,一个用户实质就是一个Socket,如果要实现私聊,可以将这些信息存储至数组里面,比如{username: username, id: id, socket: socket},通过id查找用户(Socket) 如何实现多房间聊天呢? 这个实现也很简单,还是吧用户存储在数组里面,可以创建一个二维数组Array[HomeID][UserSocket],也可以存储用户信息,可以这样Array[HomeID]{username: username, id: id, socket: socket},当用户向房间发送信息时,遍历这个房间所有用户,向遍历出来的用户逐一发送,除自己外。 上面提到的一些变量意思为: username:用户名,id:用户id,Socket和UserSocket:就是一个用户,HomeID:房间号。
1 2 npm init npm install moment --save
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 const net = require ("net" );var moment = require ('moment' );var server = net.createServer ();var users = [];var homeIDs = Array ();function count_array_num (array ){ var num = 0 ; for (let i = 0 ; i < array.length ; i++) { if (array[i] != undefined || array[i] != null ){ num++; } } return num; } server.listen (3000 , function ( ) { console .log ('Starting NChat server...' ); console .log ('http://127.0.0.1:3000' ); console .log ('' ); }) server.on ('connection' , function (socket ) { var client_data; users.push (socket); socket.on ('data' , function (data ) { console .log (data.toString ()); client_data = JSON .parse (data); if (homeIDs[client_data.homeID ] == undefined ){ homeIDs[client_data.homeID ] = Array (); } if (client_data.username == null ){ client_data.username = "User" + socket.remotePort ; } var isExist = 0 ; for (var i = 0 ; i < homeIDs[client_data.homeID ].length ; i++){ if (homeIDs[client_data.homeID ][i] != undefined && homeIDs[client_data.homeID ][i].remotePort == socket.remotePort ){ isExist = 1 ; break ; } } if (isExist == 0 ) { homeIDs[client_data.homeID ].push (socket); var time = moment (Date .now ()).format ('YYYY-MM-DD HH:mm:ss' ); console .log (time + ' ' + client_data.username + '(' + socket.remotePort + ')' + ' Join ' + client_data.homeID + ' home(Online num: ' + count_array_num (homeIDs[client_data.homeID ]) + ')' ); for (var j = 0 ; j < homeIDs[client_data.homeID ].length ; j++){ if (homeIDs[client_data.homeID ][j] != undefined && homeIDs[client_data.homeID ][j] != socket){ homeIDs[client_data.homeID ][j].write (time + ' ' + client_data.username + '(' + socket.remotePort + ')' + '加入了 ' + client_data.homeID + ' 房间(在线人数: ' + count_array_num (homeIDs[client_data.homeID ]) + ')' ); } else if (homeIDs[client_data.homeID ][j] != undefined ){ socket.write ('加入 ' + client_data.homeID + ' 房间成功! 在线人数: ' + count_array_num (homeIDs[client_data.homeID ]) + ')\n' ); } }; } for (var k = 0 ; k < homeIDs[client_data.homeID ].length ; k++){ if (homeIDs[client_data.homeID ][k] != undefined && client_data.message != undefined && homeIDs[client_data.homeID ][k] != socket){ var time = moment (Date .now ()).format ('YYYY-MM-DD HH:mm:ss' ); homeIDs[client_data.homeID ][k].write (time + '\n' + client_data.username + ': ' + client_data.message ); } } }) socket.on ('close' , function (err ) { var time = moment (Date .now ()).format ('YYYY-MM-DD HH:mm:ss' ); console .log (time + ' ' + client_data.username + '(' + socket.remotePort + ')' + ' Exit ' + client_data.homeID + ' home(Online num: ' + count_array_num (homeIDs[client_data.homeID ]) + ')' ); for (var j = 0 ; j < homeIDs[client_data.homeID ].length ; j++){ if (homeIDs[client_data.homeID ][j] == socket){ delete homeIDs[client_data.homeID ][j]; } else if (homeIDs[client_data.homeID ][j] != undefined ){ homeIDs[client_data.homeID ][j].write (time + ' ' + client_data.username + '(' + socket.remotePort + ')' + ' 退出了 ' + client_data.homeID + ' 房间(在线人数: ' + count_array_num (homeIDs[client_data.homeID ]) + ')\n' ); } } users.forEach (function (uesr ) { for (var k = 0 ; k < users.length ; k++){ if ( users[k] == socket){ delete users[k]; } } }); }) }) server.on ('error' , function ( ) { console .log ('Server error' ); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 var net = require ('net' );var data = { username : null , homeID : null , message : undefined } const argv = process.argv .slice (2 );for (let i = 0 ; i < argv.length ; i++) { switch (argv[i]){ case '-n' : data.username = argv[i +1 ]; i++; break ; case '-id' : data.homeID = argv[i +1 ]; i++; break ; case '-c' : content = argv[i +1 ]; i++; break ; case '-v' : console .log ('NChat Version: 1.0 bate' ); process.exit (0 ); case '--help' : console .log ('Usage: nchat [options]' ); console .log ('' ); console .log ('Options:' ); console .log (' -n add username' ); console .log (' -id add homeid' ); console .log (' -c send content' ); console .log (' -v get version' ); console .log (' --help help' ); process.exit (0 ); default : console .log ("Unknown parameter" ); } } var socket = net.createConnection ({ host : '127.0.0.1' , port : 3000 }) socket.on ('connect' , function ( ) { var content; socket.write (JSON .stringify (data)); if (content != null ){ data.message = content; socket.write (JSON .stringify (data)); process.exit (0 ); } process.stdin .on ('data' , function (msg ) { process.stdout .write ('\n' ); data.message = msg.toString ().slice (0 , -1 ); socket.write (JSON .stringify (data)); }); }); socket.on ('data' , function (data ) { console .log (data.toString () + '\n' ); }); socket.on ('error' , function (err ) { console .log ('Server error' ); })
编译运行 1 2 npm run start node client .js -n 昵称 -n 房间号
然后就可以在命令行上面聊天啦! 开源地址 Github