socket套接字编程

socket套接字编程

一、socket概念

  socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现, socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭). Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议,所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

二、socket通信过程

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

1334045011_9586

三、socket函数介绍

int socket(int protofamily, int type, int protocol);//返回sockfd

sockfd是描述符。

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

​ 正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。

protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

bind()函数把一个地址族中的特定地址赋给socket

函数的三个参数分别为:

sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同

 小技巧:用man 7ip迅速查找到并粘贴出来

addrlen:对应的是地址的长度。

注意:这个函数是服务器独有的,客户端不需要,因为客户端在调用connect函数的时候系统会自动分配一个本机ip+端口给他。

网络字节序与主机字节序

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

  a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

  b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。

int listen(intsocketfd,intbacklog);

此函数调用后,当客户端调用connect函数发出连接请求时,服务器端会收到此请求。

且listen函数一旦调用,此fd将变成被动套接字(今后只能等待别人来连接,而不能主动连)

内部维护了两个队列:1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手

​ 2、已完成连接的队列

后续调用的accept函数(继续往后看)会从第二个队列中取出一个连接

●socketfd:就是那个fd

●backlog:排队的最大连接个数

●返回值:0成功,-1失败

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

将客户端连接到服务器,调用connect函数后服务器的accept函数会收到这个连接的

●sockfd:就是那个fd(客户端的代言人)

●addr:要连接的服务器的地址(在调用connect之前要填充这个地址的结构体!)

●addrlen:地址长度

●返回值:成功返回0,失败返回-1

int accept(int sockfd, structsockaddr *addr, socklen_t *addrlen);

  • 参数sockfd

    参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。

  • 参数addr

    这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。

  • 参数len

    如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。

    ●返回值:成功返回客户端的fd(客户端),失败返回-1

如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。

读写函数

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

#include

wz@wz-machine:~/linux/socket/1$ cat utili.h 
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#define SERVER_PORT 9090
#define SERVER_IP "127.0.0.1"
#define LISTEN_QUEUE 5
#define BUFFER_SIZE 254

Ser.c

  1 #include"utili.h"                                                                                       
  2 
  3 int main()
  4 {
  5     //创建socket
  6     int Socket_Ser = socket(AF_INET,SOCK_STREAM,0);
  7     if(Socket_Ser == -1)
  8     {
  9         printf("Socket_ser error\n");
 10         return -1;
 11     }
 12 
 13     //协议地址
 14     struct sockaddr_in addrSer,addrCli;
 15     addrSer.sin_family = AF_INET;
 16     addrSer.sin_port = htons(SERVER_PORT);
 17     addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
 18     if( bind(Socket_Ser, (struct sockaddr*)&addrSer, sizeof(addrSer)) == -1)
 19     {
 20         printf("bind socket error\n");
 21         exit(0);
 22     }
 23     int res = listen(Socket_Ser,LISTEN_QUEUE);
 24     if(res == -1)
 25     {
 26         printf("listen error\n");
 27         return -1;
 28     }
 29     int SockConn;
 30     socklen_t len = sizeof(struct sockaddr);
 31     SockConn = accept(Socket_Ser,(struct sockaddr*)&addrCli,&len);
 32     if(SockConn == -1)
 33     {
 34         printf("SocketConnect error\n");
 35         return -1;
 36     }
 37     else
 38     {
 39         printf("Server accept Client connect ok\n");
 40         printf("Client IP:>%s\n",inet_ntoa(addrCli.sin_addr));
 41         printf("Client PORT:>%d\n",ntohs(addrCli.sin_port));
 42     }
 43                                             
 44     char sendbuf[BUFFER_SIZE];
 45     char recvbuf[BUFFER_SIZE];
 46 
 47     while(1)
 48     {
 49         printf("Ser:>");
 50         scanf("%s",sendbuf);
 51         send(SockConn,sendbuf,strlen(sendbuf)+1,0);
 52 
 53         recv(SockConn,recvbuf,BUFFER_SIZE,0);
 54         printf("Cli:>%s\n",recvbuf);
 55     }
 56     close(Socket_Ser);
 57     return 0;
 58 }
 59                                       

Cli.c

1 #include"utili.h"                                                                                       
  2 
  3 int main()
  4 {
  5     int sockCli = socket(AF_INET,SOCK_STREAM,0);
  6 
  7     struct sockaddr_in addrSer;
  8     addrSer.sin_family = AF_INET;
  9     addrSer.sin_port = htons(SERVER_PORT);
 10     addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
 11 
 12     struct sockaddr_in addrCli;
 13     addrCli.sin_family = AF_INET;
 14     addrCli.sin_port = htons(7070);
 15     addrCli.sin_addr.s_addr = inet_addr("192.168.3.252");
 16 
 17 
 18     socklen_t len = sizeof(struct sockaddr);
 19     bind(sockCli, (struct sockaddr*)&addrCli, len);
 20 
 21     int res = connect(sockCli,(struct sockaddr*)&addrSer,len);
 22     if(res == -1)
 23     {
 24         printf("Client connet Server FAIL\n");
25         return -1;
 26     }
 27     else
 28     {
 29         printf("Client connect Server OK\n");
 30     }
 31 
 32     char sendbuf[BUFFER_SIZE];
 33     char recvbuf[BUFFER_SIZE];
 34 
 35     while(1)
 36     {
 37         recv(sockCli,recvbuf,BUFFER_SIZE,0);
 38         printf("Ser:>%s\n",recvbuf);
 39 
 40         printf("Cli:>");
 41         scanf("%s",sendbuf);
 42         send(sockCli,sendbuf,strlen(sendbuf)+1,0);
 43     }
 44     close(sockCli);
 45     return 0;
 46 }                                          

结果:

wz@wz-machine:~/linux/socket/1$ ./ser
Server accept Client connect ok
Client IP:>127.0.0.1
Client PORT:>46802
Ser:>hello
Cli:>world

wz@wz-machine:~/linux/socket/1$ ./cli
Client connect Server OK
Ser:>hello
Cli:>world
Ser:>hello
Cli:>^C

在Delphi中,套接字(Socket)编程用到的基本类是TServerSocket与TClientSocket。这两个类全部位于ScktComp单元中。其类型定义如下: type TServerSocket = class (ScktComp.TCustomServerSocket); TClientSocket = class (ScktComp.TCustomSocket)。      在编程序时,首先要对TServerSocket(在服务器端)与TClientSocket(在客户端)进行实例化。对于TServerSocket的对象,主要设置其服务类型(ServerType)与端口(Port)的属性,然后编“OnClientRead”事件处理程序的代码,处理来自客户机的请求。如要启动服务器,设置TServerSocket对象的Active属性为真(即Active := True),如要停止服务器,则设置TServerSocket对象的Active属性为假(即Active := False)。      对于TClientSocket的对象,主要设置对方服务器的服务类型(ServerType)、端口(Port)以及IP地址(Address)的属性,之后编“OnConnect与OnRead”事件处理程序的代码“OnConnect”事件处理程序用来检查与服务器连接成功与否(必须在这里进行检查才有效),“OnRead”事件处理程序用来读取服务器发来的信息。如要连接服务器,设置TClientSocket对象的Active属性为真(即Active := True;注意:检查连接是否成功,必须在“OnConnect”事件处理程序中进行),如要断开与服务器的连接,则设置TClientSocket对象的Active属性为假(即Active := False)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值