SOCKET网络编程——服务器端

来源:互联网 发布:算法导论是c语言 编辑:程序博客网 时间:2024/06/07 12:01

3、socket的基本操作
  • socket()函数
  • bind()函数
  • listen()、connect()函数
  • accept()函数
  • read()、write()函数
  • close()函数



if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )

socket函数

 intsocket(int domain, int type,int protocol);

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

常用的协议族:

(1)、AF_INET:AF_INET(又称 PF_INET)是 IPv4 网络协议的套接字类型,选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址。

(2)、AF_INET6:AF_INET6 则是 IPv6 的;

(3)、AF_UNIX:AF_UNIX 则是 Unix 系统本地通信;

第二个参数int type:指定socket类型。常用的socket类有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等

(1)、SOCK_STREAM(常用的):SOCK_STREAM套接口(流套接口)的性质

1、不保留任何消息的边界

      举一个例子:本地主机通过两次独立的write(2)调用向远程主机发送数据,第一次本地进程写入25字节的数据,并通过套接口发送到远程进程,第二次再写入30字节的数据发往远程进程,总共55字节的数据,而远程进程从套接口接收数据时,将消息作为一个完整的单元来接收,或是通过若干次独立的读操作来将数据取走,即接受端并不知道这55字节的数据是分25字节和30字节两次来发送的。

2、有序性  可以保证接受的数据字节与发送是顺序完全一致(意味着通信之前必须建立一个连接)

3、无错性   可以保证接受的数据在接收端被无错的接受。如果有错误发生,在尝试完所有的错误恢复措施后仍无法消除错误,流套接口就会报告错误。所进行的错误恢复措施尝试是完全自动的,不需编程者的指导。

(2)、SOCK_DGRAM:

特征:

1、分组在发送后,可能无序地到达接收端

2、分组可能丢失。如果发生丢失,不会采取任何补救的措施,而且接受端也不必知道有分租丢失。

3、数据报分组有尺寸大小的限制,如果超出限制,在某些路由器和节点上就无法传送。

4、分组是在不建立连接的情况下被发送到远程进程的。

第三个参数protocol:故名思意,就是指定协议。常用有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议

二、struct sockaddr_in     servaddr结构体赋值



memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(DEFAULT_PORT);

先介绍下这个结构体struct sockaddr_in     servaddr的成员
{
short int sin_family;                     
unsigned short int sin_port;      
struct in_addr sin_addr;            
unsigned char sin_zero[8];       

}
成员介绍
short int sin_family:sin_family指代协议族,在socket编程中只能是AF_INET;
unsigned short int sin_port;sin_port存储端口号(使用网络字节顺序);
struct in_addr sin_addr:sin_addrs存储IP地址,一般为32位的unsigned int,其字节顺序为网络字节序,即该无符号数采用大端字节序。其中每8位表示一个IP地址中的一个数值。打印的时候可以调用inet_ntoa()函数将其转换为char*类型。
unsigned char sin_zero[8]:是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。s_addr按照网络字节顺序存储IP地址

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

htonl(INADDR_ANY):INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。
htonl函数:就是把本机字节顺序转化为网络字节顺序

h---host 本地主机
to  就是to 了
n  ---net 网络的意思
l 是 unsigned long

ntohs函数:就是把网络字节顺序转化为本机字节顺序

n——net

h——host

s——unsigned short


(3)        if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)

bind函数

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

功能描述

当用socket()函数创建套接字以后,套接字在名称空间(网络地址族)中存在,但没有任何地址给它赋值。bind()把用addr指定的地址赋值给用文件描述符代表的套接字sockfdaddrlen指定了以addr所指向的地址结构体的字节长度。一般来说,该操作称为“给套接字命名”。

通常,在一个SOCK_STREAM套接字接收连接之前,必须通过bind()函数用本地地址为套接字命名。

备注:调用bind()函数之后,为socket()函数创建的套接字关联一个相应地址,发送到这个地址的数据可以通过该套接字读取与使用。

返回值

成功,返回0;出错,返回-1,相应地设定全局变量errno。

错误

EACCESS

地址空间受保护,用户不具有超级用户的权限。

EADDRINUSE

给定的地址正被使用。

 参数说明

第1个参数sockfd:是用socket()函数创建的文件描述符;

第2个参数addr是指向一个结构为sockaddr参数的指针,sockaddr中包含了地址、端口和IP地址的信息。在进行地址绑定的时候,需要先将地址结构中的IP地址、端口、类型等结构struct sockaddr中的域进行设置之后才能进行绑定,这样进行绑定后才能将套接字文件描述符与地址等接合在一起。

第3个参数addrlen是addr结构的长度,可以设置成sizeof(struct sockaddr)。使用sizeof(struct sockaddr)来设置套接字的类型和其对应的结构。 

bind的errno值及含义

值含义备注EADDRINUSE给定地址已经使用 EBADFsockfd不合法 EINVALsockfd已经绑定到其他地址 ENOTSOCKsockfd是一个文件描述符,不是socket描述符 EACCES地址被保护,用户的权限不足 EADDRNOTAVAIL接口不存在或者绑定地址不是本地UNIX协议族,AF_UNIXEFAULTmy_addr指针超出用户空间UNIX协议族,AF_UNIXEINVAL地址长度错误,或者socket不是AF_UNIX族UNIX协议族,AF_UNIXELOOP解析my_addr时符号链接过多UNIX协议族,AF_UNIXENAMETOOLONGmy_addr过长UNIX协议族,AF_UNIXENOENT文件不存在UNIX协议族,AF_UNIXENOMEN内存内核不足UNIX协议族,AF_UNIXENOTDIR不是目录UNIX协议族,AF_UNIXEROFSsocket节点应该在制度文件系统上UNIX协议族,AF_UNIX

(4)  if( listen(socket_fd, 10) == -1)

listen:监听来自客户端的tcp socket的连接请求;

listen函数在一般在调用bind之后-调用accept之前调用,它的函数原型是:

int listen(int sockfd, int backlog);

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

错误信息:

EADDRINUSE:另一个socket也在监听同一个端口。

EBADF:参数sockfd为非法的文件描述符。ENOTSOCK:参数sockfd不是文件描述符。EOPNOTSUPP:套接字类型不支持listen操作。

参数重点说下backlog:

参数backlog是侦听队列的长度。在进程正在处理一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理的连接(还没有调用accept函数的连接),这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。

(5) if( (connect_fd = accept(socket_fd, (struct sockaddr*)&client, &LEN)) == -1)

accept函数

int accept(int sockfd, struct sockaddr* addr, socklen_t* len);

返回值是一个新的套接字描述符,他代表的是和客户端的新的连接,可以把它理解成是一个客户端的socket,这个socket包含的是客户端的ip和port。(当然这个new_socket)会从socket中继承服务器的IP和port信息。参数说明:

(1)、int sockfd:利用系统调用socket()建立的套接字描述符,通过bind()绑定到一个本地地址(一般为服务器的套接字),并且通过listen()一直在监听连接

(2)、struct sockaddr* addr: 指向struct sockaddr的指针,该结构用通讯层服务器对等套接字的地址(一般为客户端地址)填写,返回地址addr的确切格式由套接字的地址类别(比如TCP或UDP)决定;若addr为NULL,没有有效地址填写,这种情况下,addrlen也不使用,应该置为NULL;

(3)、socklen_t* len: 一个值结果参数,调用函数必须初始化为包含addr所指向结构大小的数值,函数返回时包含对等地址(一般为服务器地址)的实际数值;

备注:addrlen是个局部整形变量,设置为sizeof(struct   sockaddr_in)。

返回值

成功时,返回非负整数,该整数是接收到套接字的描述符;出错时,返回-1,相应地设定全局变量errno。

(6)、之后就是read()函数,write()函数,

n=read(int connect_fd, void*buff,size_t count);

返回值:成功返回读取的字节数,出错返回-1并设置errno,
参数就不介绍了
m = write(int connect_fd, const void *buf, size_t count);  
返回值:成功返回写入的字节数,出错返回-1并设置errno写常规文件时,write的返回值通常等于请求写的字节数
(7)、 close(connect_fd);
      close(socket_fd);

下面是我写的一个服务器程序,可以打印客户端的IP和端口号;

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#define DEFAULT_PORT 8000  #define BUFF_SIZE 4096  int main(int argc, char** argv){    int    socket_fd;    int    connect_fd;    struct sockaddr_in     servaddr;    struct sockaddr_in     client;    char   buff[BUFF_SIZE];    int    n;    int    LEN;    char addr_p[INET_ADDRSTRLEN];        //初始化Socket          if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )        {                printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);                exit(0);        }        else        {                       printf("socket ok\n");        }        //初始化          memset(&servaddr, 0, sizeof(servaddr));        servaddr.sin_family = AF_INET;        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);        servaddr.sin_port = htons(DEFAULT_PORT); //将本地地址绑定到所创建的套接字上         if( bind(socket_fd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1)        {                printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);                exit(0);        }        else        {                printf("bind ok\n");        }        //开始监听是否有客户端连接          if( listen(socket_fd, 10) == -1)        {                printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);                exit(0);        }        printf("======waiting for client's request======\n");        while(1)        {        //阻塞直到有客户端连接,不然多浪费CPU资源。                  if( (connect_fd = accept(socket_fd, (struct sockaddr*)&client, &LEN ) == -1))                {                        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);                        continue;                }                else                {                        printf("connect OK\n");                        bzero(&client, 0);                        LEN=sizeof(struct sockaddr);
inet_ntop(AF_INET, &client.sin_addr, addr_p, sizeof(addr_p));                        printf("client IP is %s,port is %d\n",addr_p, ntohs(client.sin_port));                }        //接受客户端传过来的数据          n = read(connect_fd, buff, BUFF_SIZE);        buff[n] = '\0';printf("recv msg from client: %s\n", buff);        close(connect_fd);        }        close(socket_fd);}                           

运行的结果:

[lalala@jjjjj ~]$ gcc service.c
[lalala@jjjjj ~]$ ./a.out 
socket ok
bind ok
======waiting for client's request======
connect OK
client IP is 192.168.64.128,port is 31843






























0 0
原创粉丝点击