Linux-C网络编程

简介

基础是TCP/IP协议,网上资料很多不再赘述。

推荐《图解TCP/IP》

socket编程

网络字节序

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,

接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存

因此,网络数据流的地址规定:先发出的数据是低地址,后发出的数据是高地址


TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

例如UDP段格式,地址0-1是16位的源端口号,如果这个端口号是1000(0x3e8)

则地址0是0x03,地址1是0xe8,也就是先发0x03,再发0xe8

这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址存0xe8。

但是,如果发送主机是小端字节序的,这16位被解释成0xe803,而不是1000。

因此,发送主机把1000填到发送缓冲区之前需要做字节序的转换。

同样地,接收主机如果是小端字节序的,接到16位的源端口号也要做字节序的转换。

如果主机是大端字节序的,发送和接收都不需要做转换。

同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。

网络字节序转换

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行

可以调用以下库函数做网络字节序和主机字节序的转换

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

 

h表示host,n表示network,l表示32位长整数,s表示16位短整数;

htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回;

IP地址格式转换

通常用户在表达地址时采用的是点分十进制表示的数值(或者是为冒号分开的十进制Ipv6地址)

而在通常使用的socket编程中使用的则是32位的网络字节序的二进制值,这就需要将这两个数值进行转换。

这里在 Ipv4 中用到的函数有inet_aton()inet_addr()inet_ntoa()

而 IPV4 和 Ipv6 兼容的函数有inet_pton()inet_ntop()

IPv4的函数原型:
#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

int inet_aton(const char *straddr, struct in_addr *addrptr);

char *inet_ntoa(struct in_addr inaddr);

in_addr_t inet_addr(const char *straddr);

  • 例子:
    函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值

    • 返回值:成功,则返回1,不成功返回0
    • 参数straddr:存放输入的点分十进制数IP地址字符串
    • 参数addrptr:传出参数,保存网络字节序的32位二进制数值
  • 函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址

    • 函数inet_addr():功能与inet_aton相同,但是结果传递的方式不同。
    • inet_addr()若成功则返回32位二进制的网络字节序地址
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    char ip[] = "192.168.0.101";
    struct in_addr myaddr;

    /* inet_aton 32字节序*/
    int iRet = inet_aton(ip, &myaddr);
    printf("%x\n", myaddr.s_addr);

    /* inet_addr 32字节序*/
    printf("%x\n", inet_addr(ip));

    /* inet_pton 32字节序*/
    iRet = inet_pton(AF_INET, ip, &myaddr);
    printf("%x\n", myaddr.s_addr);
    myaddr.s_addr = 0xac100ac4;

    /* inet_ntoa 输出点分十进制IP*/
    printf("%s\n", inet_ntoa(myaddr));

    /* inet_ntop 输出点分十进制IP*/
    inet_ntop(AF_INET, &myaddr, ip, 16);
    puts(ip);
    return 0;
}


域名和IP地址转换

Linux中,gethostbyname()是将主机名转化为IP地址,gethostbyaddr()则是逆操作,是将IP地址转化为主机名。

socket地址的数据类型及相关函

socket API是一层抽象的网络编程接口,适用于各种底层网络协议

如IPv4、IPv6,UNIX Domain Socket。

基于TCP协议的网络程序

TCP程序通信流程图

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,

处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,

服务器应答一个SYN-ACK段,客户端收到后从connect()返回

同时应答一个ACK段,服务器收到后从accept()返回

TCP程序实例
/*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT  8887
#define QUEUE   20
#define BUFFER_SIZE 1024

int main()
{
    ///定义sockfd
    int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);
    

//定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

///bind,成功返回0,出错返回-1
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
    perror("bind");
    exit(1);
}

printf("监听%d端口\n",MYPORT);
///listen,成功返回0,出错返回-1
if(listen(server_sockfd,QUEUE) == -1)
{
    perror("listen");
    exit(1);
}

///客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);

printf("等待客户端连接\n");
///成功返回非负描述字,出错返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
if(conn<0)
{
    perror("connect");
    exit(1);
}
printf("客户端成功连接\n");

while(1)
{
    memset(buffer,0,sizeof(buffer));
    int len = recv(conn, buffer, sizeof(buffer),0);
    //客户端发送exit或者异常结束时,退出
    if(strcmp(buffer,"exit\n")==0 || len<=0)
        break;
    printf("来自客户端数据:%s\n",buffer);
    send(conn, buffer, len, 0);
    printf("发送给客户端数据:%s\n",buffer);
}
close(conn);
close(server_sockfd);
return 0;
}

基于UDP的网络程序

/*client.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT  8887
#define BUFFER_SIZE 1024
char* SERVER_IP = "127.0.0.1";

int main()
{
    ///定义sockfd
    int sock_cli = socket(AF_INET,SOCK_STREAM, 0);
    

///定义sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT);  ///服务器端口
servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);  ///服务器ip

printf("连接%s:%d\n",SERVER_IP,MYPORT);
///连接服务器,成功返回0,错误返回-1
if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
    perror("connect");
    exit(1);
}
printf("服务器连接成功\n");
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
    printf("向服务器发送数据:%s\n",sendbuf);
    send(sock_cli, sendbuf, strlen(sendbuf),0); ///发送
    if(strcmp(sendbuf,"exit\n")==0)
        break;
    recv(sock_cli, recvbuf, sizeof(recvbuf),0); ///接收
    printf("从服务器接收数据:%s\n",recvbuf);
    
    memset(sendbuf, 0, sizeof(sendbuf));
    memset(recvbuf, 0, sizeof(recvbuf));
}

close(sock_cli);
return 0;

}

 

UDP通信流程图


UDP例程
/*server.c*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>

#define MYPORT 8887

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while (0)

void echo_ser(int sock)
{
    char recvbuf[1024] = {0};
    struct sockaddr_in peeraddr;
    socklen_t peerlen;
    int n;

while (1)
{

    peerlen = sizeof(peeraddr);
    memset(recvbuf, 0, sizeof(recvbuf));
    n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
            (struct sockaddr *)&peeraddr, &peerlen);
    if (n <= 0)
    {

        if (errno == EINTR)
            continue;

        ERR_EXIT("recvfrom error");
    }
    else if(n > 0)
    {
        printf("接收到的数据:%s\n",recvbuf);
        sendto(sock, recvbuf, n, 0,
                (struct sockaddr *)&peeraddr, peerlen);
        printf("回送的数据:%s\n",recvbuf);
    }
}
close(sock);

}

int main(void)
{
    int sock;
    if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
        ERR_EXIT("socket error");

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

printf("监听%d端口\n",MYPORT);
if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    ERR_EXIT("bind error");

echo_ser(sock);

return 0;

}
/*client.c*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define MYPORT 8887
char* SERVERIP = "127.0.0.1";
#define ERR_EXIT(m) \
    do{ \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void echo_cli(int sock)
{
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(MYPORT);
    servaddr.sin_addr.s_addr = inet_addr(SERVERIP);

int ret;
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{

    printf("向服务器发送:%s\n",sendbuf);
    sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));

    ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
    if (ret == -1)
    {
        if (errno == EINTR)
            continue;
        ERR_EXIT("recvfrom");
    }
    printf("从服务器接收:%s\n",recvbuf);

    memset(sendbuf, 0, sizeof(sendbuf));
    memset(recvbuf, 0, sizeof(recvbuf));
}

close(sock);

}

int main(void)
{
    int sock;
    if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
        ERR_EXIT("socket");

echo_cli(sock);

return 0;

}

 

转载于:https://www.cnblogs.com/wintrysec/p/10616706.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
网络编程在 ARM-Linux 上与在其他平台上的网络编程基本相同,在 ARM-Linux 上使用的套接字函数库也是基于 BSD Socket 的。下面是一个简单的网络编程实验,可以在 ARM-Linux 上运行。 首先,需要创建一个 TCP 服务器,可以使用以下代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8080 int main() { int server_fd, new_socket, valread; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); char buffer[1024] = {0}; char *hello = "Hello from server"; // Creating socket file descriptor if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // Forcefully attaching socket to the port 8080 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("setsockopt"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons( PORT ); // Forcefully attaching socket to the port 8080 if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) { perror("bind failed"); exit(EXIT_FAILURE); } if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) { perror("accept"); exit(EXIT_FAILURE); } valread = read( new_socket , buffer, 1024); printf("%s\n",buffer ); send(new_socket , hello , strlen(hello) , 0 ); printf("Hello message sent\n"); return 0; } ``` 这个程序创建了一个 TCP 服务器并绑定到 8080 端口。当客户端连接到服务器时,服务器会发送“Hello from server”字符串,然后关闭连接。 接下来,需要创建一个 TCP 客户端,可以使用以下代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8080 int main(int argc, char const *argv[]) { int sock = 0, valread; struct sockaddr_in serv_addr; char *hello = "Hello from client"; char buffer[1024] = {0}; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Socket creation error \n"); return -1; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); // Convert IPv4 and IPv6 addresses from text to binary form if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) { printf("\nInvalid address/ Address not supported \n"); return -1; } if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("\nConnection Failed \n"); return -1; } send(sock , hello , strlen(hello) , 0 ); printf("Hello message sent\n"); valread = read( sock , buffer, 1024); printf("%s\n",buffer ); return 0; } ``` 这个程序创建了一个 TCP 客户端并连接到 127.0.0.1 的 8080 端口。它发送“Hello from client”字符串,然后等待服务器的响应。 可以使用以下命令编译和运行这两个程序: ```bash arm-linux-gcc -o server server.c arm-linux-gcc -o client client.c ``` ```bash ./server & ./client ``` 这些程序应该能够在 ARM-Linux 上运行,并且客户端应该能够接收到服务器发送的“Hello from server”字符串。这个实验展示了如何在 ARM-Linux 上进行基本的网络编程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值