socket编程—技术实现

简介:
这几天都在玩socket了,有一点心得,贴出来与大家共赏,若有不妥或错误的地方,还请各位看官指点一二。

什么是socket?socket就是...,我在这里就不抄书了,有兴趣的同仁去查查书吧。
不过还要说一句,socket就是不同进程之间的一种通信方式。就象打电话是朋友之间的一种通信方式是一样。个人理解:所谓“通信”,就是相互之间发送数据。有人理解socket是不同计算机之间的一种通信方
式,这是不确切的。两个进程,不管是运行在同一台计算机上,还是运行在不同计算机上,都可通过
socket技术进行通信。

socket套接字的使用需要有网卡的支持,所以socket一般都被用来在不同机器之间通信,而如果在同一台计算机上的两个进程进行通信,通常采用效率更高的共享内存技术来实现。

两个进程之间进行通讯,就需要两个进程同时都在运行了(废话),在具体实现中,两个进程我们通常要区别对待,一个进程专门等待另一个进程给自己发消息,收到消息后进行处理,在把处理结果发送回去。我们把专门处理消息、提供服务的进程称为服务器端,把发送消息、请求处理的进程称为客户端。总体过程就是客户端发送一个消息给服务器端,服务器端进程收到消息进行处理,把处理结果发送给客户端。恩,就是这样。

还有一个问题,如果我现在有一个进程要跟另一台计算机上的某个进程进行socket通信,那在我这个进程中如何指定另一个进程呢?这里还需要说一下另一个概念——端口,如果把操作系统比作一座房子的话,那端口就是房子的窗口,是系统外界同系统内部进行通信的通道。在socket实现中,我们不进行另一个进程的指定,而是指定发送消息或接收消息的端口号。比如说现在进程A要给进程B发消息,我们会把消息发送到进程B所运行的计算机的端口N上,而进程B此时正在监视端口N,这样进程B就能收到进程A发送来的数据,同样进程B也把消息发送到该端口上,进程A也能从该端口收到进程B发送来的数据,当然,这需要客户端和服务器端关于端口号进行一个约定,即共同操作同一个端口。如果客户端把消息发送到端口N1上,而服务器端监视的是端口N2,那通信一定不能成功。端口号最大为65535,不能比这个再大了,但在我们自己的程序中尽量不要用小于1024的端口号,小于1024的端口好很多都被系统使用了,比如23被telnet所使用。

socket的实现是很简单的,只要按照一定的步骤,就可马上建立一个这样的通信通道。

下面较详细的介绍几个核心的函数:

SOCKET socket(int af, int type, int protocol);
无论是客户端还是服务器端,下面这个函数是一定要用到的,也是最先用到的。
这个函数是要告诉系统,给我准备好一个socket通道,我要和其它进程通信了。函数的返回值很重要,我们要记下来,它表示系统为我们准备好的这个socket通道,在以后的每个socket相关函数中都会用到,如果这个值等于SOCKET_ERROR,表示函数执行失败了。函数的参数我们分别给:PF_INET、SOCK_STREAM和IPPROTO_TCP。

int bind(SOCKET s, const sockaddr *addr, int namelen);
这个函数只有服务器端程序使用,作用是与某个socket通道绑定。可以用返回值判断该函数执行结果怎么样,如果等于SOCKET_ERROR,那就是失败了。第一个参数s,就是socket()函数的返回值;在结构addr中,我们要给定一个端口号;namelen等于结构sockaddr的大小。

int listen(SOCKET s, int backlog);
这个函数只有服务器端程序使用,作用是监听该端口。返回值与bind函数意义一样。

int accept(SOCKET s, sockaddr *addr, int *addrlen);
这个函数只有服务器端程序使用,作用是响应客户端的连接。返回值与bind函数意义一样。

int connect(SOCKET s, const sockaddr *name, int namelen);
这个函数只有客户端程序使用,作用是把客户端和某个计算机的某个端口建立连接。返回值与bind函数意义一样。第一个参数s,就是socket()函数的返回值;在结构name中,我们要给定一个端口号和目的机器名;namelen等于结构sockaddr的大小。

int send(SOCKET s, char *buf, int len, int flags);
int recv(SOCKET s, char *buf, int len, int flags);
这两个函数就是发送数据和接收数据,客户端和服务器端程序都能用,哪个发送哪个接收不用说了吧?呵呵。
从函数的返回值可以检查函数执行是否成功。参数中buf是指向发送或接收的数据的指针,len是数据长度。flags我们给个0就可以(其实是我不知道具体含义)。

最后就是关闭socket了,这个很容易忘掉,但这个函数很重要,一定要用。
int closesocket(SOCKET s);


好了,关键函数就这么几个,下图是这几个函数的执行顺序:

client端 service端

  |     |
  v     v
socket() socket()
  |     |
  |     v
  |   bind()
  |     |
  |     v
  |   listen()
  |     |
  |     v
  |   accept() 挂起,直到有客户端来连接
  |     |
  v   三段握手过程   |
connect() <-------------> |
  |     |
  v   发送消息   v
  +---> send() ---------------> recv() <-------+
  |   |     . |
  |   |     . 处理消息 |
  |   v   响应消息   . |
  +---- recv() <--------------- send() --------+
  |     |
  v     |
close() ---------------> recv()
    |
    v
  closesocket()

上图我觉得能很好的说明客户端和服务器端的运行轨迹。

使用以上几个函数在 linux 系统上就可成功建立一个socket通信连路,但如果在windows系统上,还要用到另一个函数:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
在windows系统上,首先要执行这个函数,所以要把这个函数放在socket()函数的前面。

我对上面的函数进行了一些封装,为节省篇幅,我去掉所有注释和非重要的函数,在这里可以看到各个函数的具体用法:

在 VC60 环境下要运行下面的函数,要包含头文件 errno.h 和 winsock2.h,还有,在连接的时候要连接上ws2_32.dll文件。

这是头文件内容:
class Socket {
public:

bool setup();

void close();

bool connect(string host, int port);

bool listen();

int accept();

int recv(char *buf, int len);

int recv(int new_fd, char *buf, int len);

int send(const char *msg, int len);

int send(int new_fd, const char *msg, int len);

private:
  int _fd;
};

这是实现文件内容:
bool Socket::setup() {

WSADATA wsd;
_fd = WSAStartup(MAKEWORD(2,2), &wsd); 
if(_fd) {
return false;
}

_fd = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_fd == -1) {
return false;
}
return true;
}

bool Socket::listen() {
struct sockaddr_in my_addr;

my_addr.sin_family = AF_INET; 
my_addr.sin_port = htons(52309);
my_addr.sin_addr.s_addr = INADDR_ANY;

if(::bind(_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == SOCKET_ERROR) {
return false;
}

if(::listen(_fd, BACKLOG) == SOCKET_ERROR) {
return false;
}

return true;
}

int Socket::accept()
{
int new_fd;
struct sockaddr_in their_addr;
int sin_size = sizeof(their_addr);

printf("accepting... \n");

new_fd = ::accept(_fd, 
  (struct sockaddr *)&their_addr,
  &sin_size);
return new_fd == SOCKET_ERROR ? -1:new_fd;
}

bool Socket::connect(string host, int port) {
struct hostent *_h = gethostbyname(host.c_str());
if (_h == 0) {
return false;
}

struct in_addr *_addr = (struct in_addr *)_h->h_addr;
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr = *_addr;
sin.sin_port = htons(port);

if (::connect(_fd, (sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR) {
return false;
}

return true;
}

int Socket::recv(int new_fd, char *buf, int len)
{
int nb = ::recv(new_fd, buf, len, 0);
if (nb == -1) {
printf("Error! recv.\n");
}
return nb;
}

int Socket::recv(char *buf, int len) {
return recv(_fd, buf, len);
}

int Socket::send(const char *msg, int len) {
return send(_fd, msg, len);
}

int Socket::send(int new_fd, const char *msg, int len)
{
int nb = ::send(new_fd, msg, len, 0);
if (nb == -1) {
printf("Error! send.\n");
}

return nb;
}

void Socket::close() {

int trytimes = 0;
while(::closesocket(_fd) && trytimes < CLOSE_TRY_TIMES)
trytimes++;

if(trytimes == 10) {
printf("Cannot close socket!\n");
}
}

好,socket类是封装好了,下面就是组织了,服务器端和客户端是不一样的,下面分别给出代码,到这里已经就很简单了。

客户端:
int main(int argc, char **argv)
{
printf("socket of client is run ...\n");
Socket s;
if (!s.connect("dezhi", 52309))
return 0;

char *msg = "ok, send a message.";
for (int i=0; i<10; i++) {
s.send(msg, 20);
printf("message = %s\n", msg);
}
s.send("q", 1);
s.close();

return 0;
}

服务器:
int main(int argc, char **argv) {
printf("socket of service is run ...\n");

Socket s;
s.listen();
int new_fd = s.accept();

char buf[8];
buf[7] = '\0';
while (1) {
if (s.recv(new_fd, buf, 5) != -1) {
  printf("%s\n", buf);
  if (buf[0] == 'q')
  break;
}
}
s.close();
}

下面为运行结果:
客户端:
socket of client is run ...
Socket: WSAStartup success execute.
Socket: socket success execute.
Socket: Establish the connection to "127.0.0.1:52309"
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
message = ok, send a message.
Socket: Close connection to "127.0.0.1:52309"
Press any key to continue

服务器端
socket of service is run ...
Socket: WSAStartup success execute.
Socket: socket success execute.
bind ok!
listen ok!
accepting...
ok, send a message.
ok, send a message.
ok, send a message.
ok, send a message.
ok, send a message.
ok, send a message.
ok, send a message.
ok, send a message.
ok, send a message.
ok, send a message.
qk, send a message.
Press any key to continue

就到这里吧。socket的相关内容可远不止这些,我在这里只是给大家来个抛砖引玉,想深究?路还很漫长。关于详细的实现代码,去我的《源码》上找吧,不放在这里,是为了让篇幅小些。
 
目录
相关文章
|
7天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
7天前
|
程序员 开发者 Python
Python网络编程基础(Socket编程) 错误处理和异常处理的最佳实践
【4月更文挑战第11天】在网络编程中,错误处理和异常管理不仅是为了程序的健壮性,也是为了提供清晰的用户反馈以及优雅的故障恢复。在前面的章节中,我们讨论了如何使用`try-except`语句来处理网络错误。现在,我们将深入探讨错误处理和异常处理的最佳实践。
|
12天前
|
网络协议 程序员 Python
pythonTCP客户端编程创建Socket对象
【4月更文挑战第6天】本教程介绍了TCP客户端如何创建Socket对象。Socket作为网络通信的基础单元,包含协议、IP地址和端口等信息。在TCP/IP中,Socket分为流式(TCP)、数据报(UDP)和原始套接字。以Python为例,创建TCP Socket对象需调用`socket.socket(AF_INET, SOCK_STREAM)`。为确保健壮性,应使用异常处理处理可能的`socket.error`。学习本教程将帮助你掌握TCP客户端创建Socket对象的技能。
|
1月前
|
网络协议 安全 API
计算机网络之Socket编程
计算机网络之Socket编程
|
2月前
|
网络协议 安全 开发者
Python 中的 Socket 编程
Python 中的 Socket 编程
42 4
|
3月前
socket编程之回声服务器函数的陷阱
由connect函数使用不当导致的小错误 话不多说先看代码:
25 0
|
3月前
|
C语言
socket编程之回声服务器
回声服务器的实现 结合我们之前对socket函数的分析,现在做一个最简单的回声服务器(由客户端输入一串字符,再由服务器端转换成大写字符回显给客户端)
26 0
|
3月前
|
API C++
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(1)
前言   本文旨在学习socket网络编程这一块的内容,epoll是重中之重,后续文章写reactor模型是建立在epoll之上的。
34 0
|
3月前
|
监控 安全 Linux
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(3)
高并发服务器模型-poll poll介绍   poll跟select类似, 监控多路IO, 但poll不能跨平台。其实poll就是把select三个文件描述符集合变成一个集合了。
35 0
|
22天前
|
网络协议 Perl
Perl 教程 之 Perl Socket 编程 6
Perl Socket教程展示了如何进行网络通信。服务端(server.pl)创建一个TCP套接字,绑定到端口7890并监听,接收客户端连接并发送消息。客户端(client.pl)连接到服务端,接收并打印消息。在两个不同终端上分别运行服务端和客户端可实现交互。
24 2