linux网络编程学习1

准备好好学习网络编程,这将是一个稍微漫长的过程(因为还有好多别的事情要做)。

目前学习思路是:例子—>书—>总结—>书

今天的例子是:建立一个服务器与客户端的连接,能接收和发送消息就行了。(read和write的部分我还没看,以后再补)

其实,有过java网络编程经验的童鞋,再看这些内容,会发现很多地方都是通的。

1、代码先行

第一个文件:server.c

#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <stdlib.h>
#define MAX_LINE 100
#define INET_ADDRDTRLEN 16
/**
    一个转换函数,无关紧要
*/
void my_fun(char *p)
{
    if(p == NULL)
        return;
    for(; *p!=\0;p++)
        if(*p>=A && *p <=Z)
            *p = a+ (*p - A);
}

int main(void)
{
    
    struct sockaddr_in sin;//服务端地址结构
    struct sockaddr_in cin;//客户端地址结构
    
    int l_fd,c_fd;//l_fd:listener的套接字,c_fd:连接的套接字
    socklen_t len;
    
    char buf[MAX_LINE];//存储从客户端发送的内容
    char addr_p[INET_ADDRDTRLEN];//存储客户端的ip

    int port = 8000;//服务器的端口号
    int n;//读写字节数

    bzero(&sin,sizeof(sin));
    sin.sin_family = AF_INET;//ipv4
    sin.sin_addr.s_addr = INADDR_ANY;//接受任何地址
    sin.sin_port = htons(port);//服务器监听的端口号
    l_fd = socket(AF_INET,SOCK_STREAM,0);//建立监听套接字
    int b = bind(l_fd,(struct sockaddr *)&sin,sizeof(sin));//将地址和套接字绑定
    if(0!=b)
    {
        printf("failed to bind\n");
        exit(1);
    }
    listen(l_fd,10);//开始监听

    printf("wait..\n");
    
    while(1)
    {
        c_fd = accept(l_fd,(struct sockaddr *)&cin,&len);/*如果没有客户端的请求,就会阻塞,如果有的话,就可以开始通信了,并返回连接套接字*/
    
        n = read(c_fd, buf, MAX_LINE);

        inet_ntop(AF_INET, &cin.sin_addr, addr_p, sizeof(addr_p));/*将客户端地址(cin.sin_addr)转成十进制的字符串(addr_p)*/
        
        printf("client ip is %s,port is %d\n",
            addr_p, ntohs(cin.sin_port));/*ntohs将二进制的sin_port转为十进制*/
        printf("content is: %s\n", buf);
        
        my_fun(buf);
        
        write(c_fd, buf, n);//写回
        
        close(c_fd);//关闭套接字
                
        
    }
    
    if(close(l_fd)==-1)
    {
        perror("fail to close\n");
        exit(1);
    }

    return 0;
}

第二个文件:client.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <stdlib.h>
#define MAX_LINE 100

int main(int argc, char * argv[])
{    
    struct sockaddr_in sin;//linux中的网络通信地址结构

    char buff[MAX_LINE];

    int sfd;//套接字描述符
    int port = 8000;//服务端端口

    char *str = "test string";
    if(argc > 1)
    {
        str = argv[1];
    }
    bzero(&sin,sizeof(sin));//清空
    sin.sin_family = AF_INET;//地址族设为ipv4
    inet_pton(AF_INET,"127.0.0.1",&sin.sin_addr);//将十进制的ip地址转为二进制

    sin.sin_port = htons(port);//将port转为网络字节序
    sfd = socket(AF_INET,SOCK_STREAM, 0);//创建套接字,并返回套接字描述符
    int c = connect(sfd,(struct sockaddr *)&sin,sizeof(sin));//建立连接,传入套接字描述符,地址,地址长度
    if(0!=c)
    {
        printf("failed to connect!\n");
        exit(1);
    }
    int len = strlen(str);
    write(sfd,str,len+1);
    read(sfd,buff,MAX_LINE);
    printf("recive from server: %s\n", buff);
    close(sfd);//关闭socket

    return 0;
}

第三个文件makefile

all:server client

server:server.c
    gcc -o server server.c
client:client.c
    gcc -o client client.c
clean:
    rm server client
clean~:
    rm *~

2、运行截图

编译

运行

3、分析过程

服务端:

(1)利用sockaddr_in设置监听地址(地址+端口)

(2)利用socket创建一个套接字

(3)利用bind将套接字(2)与监听地址(1)绑定起来

(4)利用listen开始监听

(5)利用accept()进行接收连接(此时会生成一个新的套接字,与监听套接字不同)

(6)进行read和write操作

客户端:

(1)利用sockaddr_in设置服务器地址

(2)利用socket创建一个套接字

(3)利用connect连接服务器,(2)中返回的套接字就是这个标识

4、函数API

4.1 基础

(1)字节转换

在网络环境中,进程之间的通信时要跨越主机的,因此就会带来字节序不统一的问题。为了解决这个问题,网络协议提供了一种字节序,传输的时候需要先进行转换。

(网络字节序实际上是大端存储,理论上如果你的机器是大端存储的,那么可以不转换,但是一般会考虑到程序移植,此时就会出问题了,因此建议在传输前都要转换一下)

头文件:#include <arpa/inet.h>

//net to host
unit32_t ntohl(unit32_t netint32);
unit16_t ntohs(unit16_t netint16);
//host to net
unit32_t htonl(unit32_t hostint32);
unit16_t htons(unit16_t hostint16);

(2)地址

头文件:#include <netinet/in.h>

/*表示一个ipv4地址,二进制的*/
struct in_addr
{
    in_addr_t s_addr;//无符号整型
};
/*表示一个地址*/
struct sockaddr_in
{
    sa_family_t sin_family; /*16位地址协议族*/
    int port_t sin_port; /*16位的端口号*/
    struct in_addr sin_addr; /*32位的ip地址*/
    unsigned char sin_zero[8]; /*填充区:8字节*/
};
/*表示一个地址,常用作参数*/
struct sockaddr
{
    sa_family_t sin_family; /*16位地址协议族*/
    char sa_data[14]; /*填充区:14字节*/
};

(3)地址转换(十进制与二进制)

头文件:#include <netinet/in.h>

/*
    ntop & pton
    ntop 二进制转十进制
    pton 十进制转二进制
*/
const char* inet_ntop(int domain, const void* restrict_addr, char * restrict_str, socketlen_t size);
int inet_pton(int domain, const char* restrict_str, void* restrict addr);

(4)主机信息的获取

头文件:#include <netdb.h>

struct hostent
{
    char * h_name;/*主机名,每个主机只有一个*/
    char ** h_aliases;/*主机别名列表*/
    int h_addrtype;/*ip地址类型:ipv4 or ipv6*/
    int h_length;/*ip地址长度,如果是ipv4则为32位*/
    char **h_addr_list;/*ip地址列表,h_addr_list[0]为主机的ip地址*/
};

(5)地址映射(DNS)

头文件:#include <sys/socket.h> #include <netdb.h>

/*
hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。
        举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
返回值:0 成功,非0 出错
关于这个函数的一个讲解:http://www.cnblogs.com/cxz2009/archive/2010/11/19/1881693.html
*/
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );

4.2 socket初识

(1)建立、销毁套接字描述符

头文件:#include <sys/socket.h>

/*
    domain:套接字域
    type:套接字类型
    protocol:与特定的地址家族相关的协议,如果指定为0,那么系统就会根据地址格式和套接类别,自动选择一个合适的协议.这是推荐使用的一种选择协议的方法.
*/
int socket(int domain, int type, int protocol);

如果返回-1则创建套接字失败,否则返回套接字。

socket函数就像open函数一样,得到套接字后,就可以像操作文件一样操作套接字了,一般来说使用完了之后,要用close()关闭掉。

表1 套接字域描述

域名 域的作用
AF_INET  ipv4
AF_INET6  ipv6 
AF_UNIX  用于非网络环境的进程通信 
AF_UNSPIC  未指定域 

表2 套接字类型描述与该套接字对应的默认协议

套接字类型 类型描述 默认协议
SOCK_DGRAM 长度固定的,无连接报文 UDP
SOCK_RAW 原始套接字 绕过协议
SOCK_SEQPACKET 长度固定的,面向连接报文 SCTP
SOCK_STREAM 有序的,面向连接的字节流 TCP

(2)地址绑定

头文件:#include <sys/socket.h>

如果只有一个套接字,我们还是什么都做不了,因此需要将套接字和地址绑定起来才可以进行网络通信。

/*
   sockfd: 监听套接字描述符
    addr:将要绑定的地址(是sockaddr类型的)
    len:地址的长度
*/
int bind(int sockfd, const struct sockaddr * addr, socklen_t len);

(3)建立连接

头文件:#include <sys/socket.h>

客户端:

/*
   sockfd:套接字描述符
    addr:一般而言是服务器的地址
    len:地址的长度
    返回值:0 成功 非0 失败,成功后对sockfd的操作,就想到与跟server的通信了
*/
int connect(int sockfd, const struct sockaddr* addr, socklen_t len);

服务端:

/*
    sockfd:套接字描述符,监听的套接字描述符
    backlog:最多可以排队等待连接的请求数量
    返回值:0 监听成功 非0 失败
*/
int listen(int sockfd, int backlog);
/*
    sockfd:套接字描述符,一般是监听的套接字描述符
    addr:地址结构,一般是客户端的地址(可以填NULL)
    len:地址长度(可以填NULL)
    返回:成功返回新的套接字文件描述符 失败则返回-1,之后对套接字的操作,就相当于对client端的通信了
*/
int accept(int sockfd, struct sockaddr* addr, socklen_t len);

总结:

通过这个程序,算是了解一点点网络编程的知识了,等把这些例子都看完了,再去看APUE应该会好受一点了吧。

linux网络编程学习1,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。