linux 系统编程 --socket 服务器端(十一)

struct sockaddr 结构体,早期网络编程函数都是基于该结构体,但是随着技术的发展,ipv4协议诞生,为了向前兼容,现在sockaddr退化成了void * 作用的指针,内部会强制类型转换为所需的地址类型(sockaddr_in或者sockaddr_un或者scokaddr_in6)
sockaddr_in代表AF_INET,ipv4协议,sockaddr_un代表AF_UNIX,scokaddr_in6代表AF_INET6

定义的时候,应该定义成struct sockaddr_in,,而在实际使用时,传递参数需要强制转换一下struct sockaddr *。

bind函数,accept函数,connect函数调用时会遇到这个问题。

           struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr   sin_addr;   /* internet address */
           };

sin_port端口号,注意网络字节序与本机字节序的转换。
sin_addr ip地址,注意网络字节序与本机字节序的转换。它是个结构体,成员只有一个是

 struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

它在赋值时,可能有以下几种情况

struct sockaddr_in addr;
addr.sin_family= AF_INET/AFINET6;
addr.sin_port=htons/ntohs;
addr.sin_addr.s_addr=inet_pton/inet_ntop;

核心函数socket(int domain, int type, int protocol);

#include          
#include 

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

第一个参数domain取值:
AF_INET
AF_INET6
AF_UNIX (本地套接字)

第二个参数type取值:
SOCK_STREAM 可靠的,基于顺序的字节流TCP。
SOCK_DGRAM 无连接的、不可靠的udp
SOCK_SEQPACKET 双线路,可靠,发送固定长度的数据包
SOCK_RAW 使用ICMP公共协议

第三个参数一般取0,表示使用默认。

函数调用成功返回新创建的socket文件描述符,失败返回-1

bind函数

 #include         
 #include 

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

第一个参数使用调用socket函数的返回值,第二个参数使用定义好的sockaddr*结构体,第三个参数是结构体的长度。
成功返回0,失败返回-1.

listen函数:

 #include           /* See NOTES */
 #include 

 int listen(int sockfd, int backlog);

该函数并不做阻塞监听,而是设置一个“同时”能够建立连接的最大数量。
注意,它也不是设置最大支持连接。能够支持的最大连接数量是系统内核决定。
因为连接建立需要3次握手,这个过程需要时间,listen设置的是该时间内同时允许建立握手队列量。

accept 函数:

 #include          
 #include 

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

第2个参数是传出参数,返回链接客户端的地址,包括ip与端口号,无需自己去初始化。
成功返回一个新的socket文件描述符,与前面socket函数返回的描述符不是一个东西。
新的文件描述符用于和客户端通信。、

connect函数:

 #include          
 #include 

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

linux 系统编程 --socket 服务器端(十一)_第1张图片
差异一:客户端不用bind绑定端口号,而是用connect来连接绑定,这两个函数的参数基本相同。服务器端用bind绑定ip地址与端口号。
差异二:服务器端多了listen和accept两个函数。

尝试编写服务器端程序:

#include 
#include 
#include 
#include 
#include 

#define SERV_PROT 6666
#define SERV_IP   "127.0.0.1"

int main()
{
    int serfd;
    struct sockaddr_in serv_addr;
 //填充结构体,注意转换
    serv_addr.sin_fmaily=AF_INET;
    serv_addr.sin_port=htons(SERV_PROT);
    serv_addr.sin_addr.s_addr=inet_pton(SERV_IP);


    serfd=socket(AF_INET,SOCK_STREAM,0);
    //绑定,强制类型转换
    bind(serfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    return 0;
}

添加listen,128是默认值。
添加accept,注意函数的用法。

#include 
#include 
#include 
#include 
#include 

#define SERV_PROT 6666
#define SERV_IP   "127.0.0.1"

int main()
{
    int serfd,clifd;
    struct sockaddr_in serv_addr,cli_addr;
    socklen_t cli_addr_len;
    cli_addr_len=sizeof(cli_addr);
  //填充结构体
    serv_addr.sin_fmaily=AF_INET;
    serv_addr.sin_port=htons(SERV_PROT);
    serv_addr.sin_addr.s_addr=inet_pton(SERV_IP);



    serfd=socket(AF_INET,SOCK_STREAM,0);
    bind(serfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    listen(serfd,128);
   //用clifd接受accept的返回,第二个参数是传出参数,接受客户端的ip和端口,第三个参数是结构体大小。
   //注意加上取地址符,因为是传出参数。
    clifd=accept(serfd,&cli_addr,&cli_addr_len);
    return 0;
}

添加业务逻辑,把每一个小写的字符都转换成大写字符。
添加必备的头文件。
修改几处bug,比如inet_pton使用错误,accept使用错误

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SERV_PROT 6666
#define SERV_IP   "127.0.0.1"

int main()
{
    int serfd,clifd;
    struct sockaddr_in serv_addr,cli_addr;
    socklen_t cli_addr_len;
    cli_addr_len=sizeof(cli_addr);
    char buf[BUFSIZ];
    int i,n;
    char * dst;

    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(SERV_PROT);
    //有两种写法,一种是用inet_pton,另一种是利用已经定义好的宏
  //serv_addr.sin_addr.s_addr=inet_pton(AF_INET,SERV_IP,dst);
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

    serfd=socket(AF_INET,SOCK_STREAM,0);
    bind(serfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));

    listen(serfd,128);

    clifd=accept(serfd,(struct sockaddr*)&cli_addr,&cli_addr_len);

//业务逻辑
    n=read(clifd,buf,sizeof(buf));

    for(i=0;i

编译成功,如何测试服务器能否正确运行呢?
可以使用nc命令来模拟客户端,运行server程序,再开一个终端。
linux 系统编程 --socket 服务器端(十一)_第2张图片
实验成功。

你可能感兴趣的:(ubuntu下学习c语言)