linux高级编程笔记(十)——socket编程

目录

一、C/S模型和B/S模型

二、预备知识

1 网络字节序

2 IP地址转换函数

3 sockaddr数据结构

三、网络套接字(socket)

1 socket模型创建流程图

2 socket函数 

2 bind函数

3 listen函数 

4 accept函数 

5 connect函数

四、代码实现

1 server端

2 client端


一、C/S模型和B/S模型

C/SB/S
优点

缓存大量数据、协议选择灵活、

速度快

安全性、跨平台、开发工作量小
缺点安全性不能缓存大量数据、严格遵守http

二、预备知识

1 网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,那么如何定义网络数据流的地址呢?发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。

上图为数据0xabcd分别在大端字节序和小端字节序的存储方式。小端字节序转为大端字节序,只需要按字节的形式,将小端(低地址)的数据存放在大端(高地址),实际的转换流程如下图所示。

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);//本地字节序--->网络字节序(IP)
uint16_t htons(uint16_t hostshort);//本地字节序---->网络字节序(port)
uint32_t ntohl(uint32_t netlong);//网络字节序--->本地字节序(port)
uint16_t ntohs(uint16_t netshort);//网络字节序--->本地字节序(IP)
h表示host,n表示network,l表示32位长整数,s表示16位短整数。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

2 IP地址转换函数

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
支持IPv4和IPv6
可重入函数

描述:

本地字节序(string ip)--->网络字节序

参数:

af AF_INET、AF_INET6

src 传入,IP点分十进制字符串

dst 传出,转换后的网络字节序IP

#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

描述:

网络字节序--->本地字节序(string ip)

参数:

af AF_INET、AF_INET6

src 传入,网络字节序IP

dst 传出,IP点分十进制字符串

3 sockaddr数据结构

strcut sockaddr很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。数据结构定义如下所示:

man 7 ip

struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};

struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET/AF_INET6 */
               in_port_t      sin_port;   /* 端口的网络字节序 。htons(9527)*/

                /*ip地址的网络字节序。

                   int dst;

                   inet_pton(AF_INET, "192.168.0.1", (void *)&dst);

                   sin_addr.s_addr  = dst;

                    以上三步即可,不过一般使用宏定义INADDR_ANY。可以去除系统中有效的任意IP地址。

                    sin_addr.s_addr = htonl(INADDR_ANY);

*/
               struct in_addr sin_addr;   /*ip地址的网络字节序。int dst; inet_pton(AF_INET, "192.168.0.1", (void *)&dst); sin_addr.s_addr  = dst*/
};

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

三、网络套接字(socket)

1 socket模型创建流程图

2 socket函数 

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

参数:

domain 通信使用的协议版本,可以有如下选项

AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址

AF_INET6 与上面类似,不过是来用IPv6的地址

AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用

type 协议类型,可以有如下选项

SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。

SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。

SOCK_SEQPACKET 这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。

SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)

SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

protocol 0 默认协议
返回值:

成功返回一个新的文件描述符,失败返回-1,设置errno

2 bind函数

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

描述:

绑定地址结构(IP+端口)

参数:

sockfd socket文件描述符

addr 构造出IP地址加端口号

addrlen sizeof(addr)长度

返回值:

成功返回0,失败返回-1, 设置errno 

常见的使用方式如下所示:

struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8000);

3 listen函数 

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

描述:

设置最大监听数

参数:

sockfd socket文件描述符

backlog 排队建立3次握手队列和刚刚建立3次握手队列的链接数和

4 accept函数 

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

描述:

设置最大监听数

参数:

sockfd socket文件描述符

addr 传出。成功与服务器建立连接的那个客户端的地址结构

addrlen 传入传出。入:addr的大小。出,客户端addr实际大小

返回值:

成功返回能与服务器进行数据通信的socket对应的文件描述符,失败返回-1,并设置errno。

5 connect函数

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

描述:

使用现有的socket与服务器建立连接

参数:

sockfd socket函数的返回值

addr 传入,服务器的地址结构

addrlen addr的大小

四、代码实现

1 server端

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;

    int listenfd, connfd;

    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    int i, n;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 128);

    printf("Accepting connections ...\n");

    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

    while (1) {

        n = read(connfd, buf, sizeof(buf));

        write(STDOUT_FILENO,buf,n);

        for (i = 0; i < n; i++)
            buf[i] = toupper(buf[i]);

        write(connfd, buf, n);
    }
    close(connfd);

    return 0;
}

2 client端

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

#define SERV_PORT 8000

void sys_err(const char *str)
{
        perror(str);
        exit(1);
}

int main(int argc, char *argv[])
{
    int cfd;
    int conter = 10;
    char buf[BUFSIZ];

    struct sockaddr_in serv_addr;          //服务器地址结构

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    //inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);

    cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1)
        sys_err("socket error");

    int ret = connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    if (ret != 0)
        sys_err("connect err");

    while (--conter) {
        write(cfd, "hello\n", 6);
        ret = read(cfd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);
        sleep(1);
    }

    close(cfd);

    return 0;
}

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 很抱歉,我是AI语言模型,无法提供自己的笔记。但是,我可以为您提供一些关于Linux网络编程的信息。Linux网络编程是指在Linux操作系统上进行网络编程的技术,主要包括socket编程网络协议、网络编程模型等内容。在Linux网络编程中,常用的编程语言有C、C++、Python等。Linux网络编程的应用非常广泛,可以用于开发各种网络应用程序,如Web服务器、邮件服务器、FTP服务器等。同时,Linux网络编程也是网络安全领域的重要技术之一,可以用于网络攻防、数据加密等方面。 ### 回答2: Linux网络编程是一门高级语言编程,包括了网络以及套接字的知识,它的目的是让开发者能够在Linux系统下进行网络应用程序的开发与运行。Linux网络编程中,主要涉及到如下的知识: 1.网络协议:网络协议是数据在计算机网络内传输时所必须遵循的规则和约定。网络协议的共同目标是确保数据的可靠传输。 2.套接字:套接字是一种在网络编程中广泛使用的编程接口,它允许从一个进程向另一个进程通信。通过套接字的编程,可以实现网络上的客户端和服务器端的通信。 3.套接字选项:在套接字编程中,选项提供了一些可选项来控制套接字的行为。例如,可以使用SO_REUSEADDR选项来允许相同的IP地址和端口被多个套接字所使用,或者使用SO_LINGER选项来控制套接字在关闭时的行为。 4.网络编程的主要函数:对于网络编程而言,大多数使用的函数都是socket编程或一些与之相关的函数。如socket、bind、listen、accept、connect、send、recv等。 5.多线程编程和select函数:在网络编程中,常常需要使用多线程编程来同时处理多个套接字,使程序具有高并发性。而select函数可以在一个线程中监听多个套接字的I/O事件,从而优化服务器的性能和响应速度。 6.网络编程的实际应用:网络编程在实际应用中,可以实现许多有趣的功能。例如,可以创建简单的聊天程序、实现网络文件传输、远程控制、实现P2P通信等。 总之,Linux网络编程是一种非常重要的技术,要学习并掌握这门技术,需要掌握网络协议、套接字、多线程编程等基础知识。掌握这些知识后,开发者可以根据实际需求,灵活地使用网络编程技术来实现各种基于网络的应用程序。 ### 回答3: Linux网络编程在现代软件开发中扮演着非常重要的角色,这是因为它是一种跨平台的操作系统,并且为开发人员提供了良好的网络编程接口。以下是一些重要的技术和笔记: 1. 套接字(socket)编程—— 在Linux环境中,套接字是网络编程的关键要素。它被用于实现客户端/服务器应用程序中的通信,例如Web服务器和聊天室应用程序。在编写套接字程序时,必须使用包括bind,listen和accept等操作来处理连接请求。 2. 网络协议—— Linux支持各种网络协议,例如TCP/IP,UDP,ICMP,ARP和RIP等。其中TCP/IP是最常用的网络协议,因为它可靠且易于使用,在开发网络应用程序时需要具备其相关知识,例如TCP连接管理和协议数据包的格式化。 3. 多线程编程—— 在Linux环境中,多线程编程是一种非常重要的技术,可以同时处理多个网络请求,以提高应用程序的性能。通常使用POSIX线程库(pthread)来实现多线程编程,并使用同步和互斥机制来管理线程访问共享变量的冲突。 4. 网络安全—— 网络安全是Linux网络编程的一个重要方面,因为网络应用程序通常需要保护敏感数据和隐私信息。开发人员必须学习诸如SSL和TLS等加密协议,以确保数据传输的安全性。 总结来说,在Linux环境下进行网络编程需要熟悉套接字编程网络协议、多线程编程网络安全等技术。这些技术的结合可以实现高效的网络应用程序,并提高用户体验。在掌握这些技术后,开发人员可以将网络编程应用于Web服务器、聊天室应用程序、数据存储器等各种应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值