Linux网络编程笔记

1.C/S

2.各函数:

网络字节序:

    小端法:(pc本地存储)    高位存高地址。地位存低地址。    int a = 0x12345678

    大端法:(网络存储)    高位存低地址。地位存高地址。

    htonl --> 本地--》网络 (IP)            192.168.1.11 --> string --> atoi --> int --> htonl --> 网络字节序

    htons --> 本地--》网络 (port)

    ntohl --> 网络--》 本地(IP)

    ntohs --> 网络--》 本地(Port)

IP地址转换函数:

    int inet_pton(int af, const char *src, void *dst);        本地字节序(string IP) ---> 网络字节序

        af:AF_INET、AF_INET6

        src:传入,IP地址(点分十进制)

        dst:传出,转换后的 网络字节序的 IP地址。 

        返回值:

            成功: 1

            异常: 0, 说明src指向的不是一个有效的ip地址。

            失败:-1
    
       const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);    网络字节序 ---> 本地字节序(string IP)

        af:AF_INET、AF_INET6

        src: 网络字节序IP地址

        dst:本地字节序(string IP)

        size: dst 的大小。

        返回值: 成功:dst。     

            失败:NULL

1.socket()

socket函数:

    #include <sys/socket.h>

    int socket(int domain, int type, int protocol);        创建一个 套接字

        domain:AF_INET、AF_INET6、AF_UNIX

        type:SOCK_STREAM、SOCK_DGRAM

        protocol: 0 

        返回值:
            成功: 新套接字所对应文件描述符

            失败: -1 errno

2.bind()

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);  
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
serv_addr.sin_family = AF_INET;  //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
serv_addr.sin_port = htons(1234);  //端口
//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

sockaddr_in 结构体

接下来不妨先看一下 sockaddr_in 结构体,它的成员变量如下

struct sockaddr_in{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};

1) sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。

2) sin_prot 为端口号。uint16_t 的长度为两个字节,理论上端口号的取值范围为 0~65536,但 0~1023 的端口一般由系统分配给特定的服务程序,例如 Web 服务的端口号为 80,FTP 服务的端口号为 21,所以我们的程序要尽量在 1024~65536 之间分配端口号。

端口号需要用 htons() 函数转换,后面会讲解为什么。

3) sin_addr 是 struct in_addr 结构体类型的变量,下面会详细讲解。

4) sin_zero[8] 是多余的8个字节,没有用,一般使用 memset() 函数填充为 0。上面的代码中,先用 memset() 将结构体的全部字节填充为 0,再给前3个成员赋值,剩下的 sin_zero 自然就是 0 了。 

in_addr 结构体

sockaddr_in 的第3个成员是 in_addr 类型的结构体,该结构体只包含一个成员,如下所示: 

struct in_addr{
    in_addr_t  s_addr;  //32位的IP地址
};

3.listen()  

int listen(int sockfd, int backlog);        设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)

        sockfd: socket 函数返回值

        backlog:上限数值。最大值 128.


        返回值:

            成功:0

            失败:-1 errno    

4.accept()

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);    阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符。

        sockfd: socket 函数返回值

        addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)

            socklen_t clit_addr_len = sizeof(addr);

        addrlen:传入传出。 &clit_addr_len

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

        返回值:

            成功:能与客户端进行数据通信的 socket 对应的文件描述。

            失败: -1 , errno

5.connect()

 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);      使用现有的 socket 与服务器建立连接

        sockfd: socket 函数返回值

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

            srv_addr.sin_family = AF_INET;

            srv_addr.sin_port = 9527     跟服务器bind时设定的 port 完全一致。

            inet_pton(AF_INET, "服务器的IP地址",&srv_adrr.sin_addr.s_addr);

        addr:传入参数。服务器的地址结构

            
        addrlen:服务器的地址结构的大小

        返回值:

            成功:0

            失败:-1 errno

        如果不使用bind绑定客户端地址结构, 采用"隐式绑定".

6.转换函数 

// 点分十进制IP -> 大端整形  only ipv4
in_addr_t inet_addr (const char *cp);
 
// 大端整形 -> 点分十进制IP only ipv4
char* inet_ntoa(struct in_addr in);



例如 inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));


#include <arpa/inet.h>


// 主机字节序的IP地址是字符串, 网络字节序IP地址是整形
//点分十进制IP -> 大端整形

int inet_pton(int af, const char *src, void *dst); 

// 大端整形 -> 点分十进制IP
      
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

param

说明

af

AF_INET: ipv4 格式的 ip 地址

AF_INET6: ipv6 格式的 ip 地址

Src

传入参数,对应要转换的点分十进制的 ip 地址: 192.168.1.100

dst

传出参数,函数调用完成,转换得到的大端整形 IP 被写入到这块内存中

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

param

说明

af

AF_INET: ipv4 格式的 ip 地址

AF_INET6: ipv6 格式的 ip 地址

Src

传入参数,这个指针指向的内存中存储了大端的整形 IP 地址

dst

传出参数,存储转换得到的小端的点分十进制的 IP 地址

size

修饰 dst 参数的,标记 dst 指向的内存中最多可以存储多少个字节

成功:指针指向第三个参数对应的内存地址,通过返回值也可以直接取出转换得到的 IP 字符串

失败: NULL

7.服务器程序和客户端程序(C键盘输入,S屏幕输出)

Socket本意为“插座”,在Linux下,用于表示进程间网络通信的特殊文件类型,本质为内核借助缓冲区形成的伪文件

#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <iostream>
using namespace std;
#define SERV_PORT 9000


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

int main(int argc, char *argv[])
{
    int lfd = 0, cfd = 0;
    int ret, i;
    char buf[1024], client_IP[1024];

    struct sockaddr_in serv_addr, clit_addr;  // �����������ַ�ṹ �� �ͻ��˵�ַ�ṹ
    socklen_t clit_addr_len;				  // �ͻ��˵�ַ�ṹ��С

    serv_addr.sin_family = AF_INET;				// IPv4
    serv_addr.sin_port = htons(SERV_PORT);		// תΪ�����ֽ���� �˿ں�
    //serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// ��ȡ����������ЧIP
	//serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr);
    lfd = socket(AF_INET, SOCK_STREAM, 0);		
    if (lfd == -1) {
        sys_err("socket error");
    }

    bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    listen(lfd, 128);					

    clit_addr_len = sizeof(clit_addr);	

    cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);	
    if (cfd == -1)
        sys_err("accept error");
//printf("%s,%d\n", inet_ntoa(clit_addr.sin_addr),ntohs(clit_addr.sin_port));
 inet_ntop(AF_INET, &clit_addr.sin_addr, client_IP, sizeof(client_IP));
 cout<<client_IP<<"  "<<ntohs(clit_addr.sin_port)<<endl;
   // printf("client ip:%s port:%d,clint_IP=%s\n", 
            //inet_ntop(AF_INET, &clit_addr.sin_addr, client_IP, sizeof(client_IP)), 
           // ntohs(clit_addr.sin_port),client_IP);	//inet_ntop返回第三个参数,字符数组。
    while (1) {
        ret = read(cfd, buf, sizeof(buf));		
        write(1, buf, ret);								
    }

    close(lfd);
    close(cfd);

    return 0;
}
#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 9000

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

int main(int argc, char *argv[])
{
    int cfdd;
    int conter = 10;
    char buf[1024];
    
    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);

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

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

    while (1) {
       
        ret = read(0, buf, sizeof(buf));
        write(cfdd, buf, ret);
       
    }

    close(cfdd);

	return 0;
}

8.多线程并发服务器

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

struct s_info {                     //定义一个结构体, 将地址结构跟cfd捆绑
    struct sockaddr_in cliaddr;
    int connfd;
};

void *do_work(void *arg)
{
    int n,i;
    struct s_info *ts = (struct s_info*)arg;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];      //#define INET_ADDRSTRLEN 16  可用"[+d"查看

    while (1) {
        n = Read(ts->connfd, buf, MAXLINE);                     //读客户端
        if (n == 0) {
            printf("the client %d closed...\n", ts->connfd);
            break;                                              //跳出循环,关闭cfd
        }
        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
                ntohs((*ts).cliaddr.sin_port));                 //打印客户端信息(IP/PORT)

        for (i = 0; i < n; i++) 
            buf[i] = toupper(buf[i]);                           //小写-->大写

        Write(STDOUT_FILENO, buf, n);                           //写出至屏幕
        Write(ts->connfd, buf, n);                              //回写给客户端
    }
    Close(ts->connfd);

    return (void *)0;
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    pthread_t tid;

    struct s_info ts[256];      //创建结构体数组.
    int i = 0;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);                     //创建一个socket, 得到lfd

    bzero(&servaddr, sizeof(servaddr));                             //地址结构清零
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);                               //指定本地任意IP
    servaddr.sin_port = htons(SERV_PORT);                                       //指定端口号 

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));             //绑定

    Listen(listenfd, 128);                                                      //设置同一时刻链接服务器上限数

    printf("Accepting client connect ...\n");

    while (1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);   //阻塞监听客户端链接请求
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;

        pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
        pthread_detach(tid);                                                    //子线程分离,防止僵线程产生.
        i++;
    }

    return 0;
}

 查看连接状态 netstat -apn|grep 端口号


2MSL时长:

    一定出现在【主动关闭连接请求端】。 --- 对应 TIME_WAIT 状态。

    保证,最后一个 ACK 能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)

9.多路I/O转接 

select:把文件描述符集合从用户态拷贝到内核态,当有事件发生的时候,内核态返回到用户态,在用户态遍历发生的集合;

epoll:在内核态创建文件描述符集合监听,有事件发生的时候,直接把发生事件的描述符放入一个就继续缓存区返回给用户态,用户态不需要遍历去查询是哪个事件,精确定位哪些文件描述符有事件发生 

epoll服务器端 

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

int main() {

    // 创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 绑定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 监听
    listen(lfd, 8);

    // 调用epoll_create()创建一个epoll实例
    int epfd = epoll_create(100);

    // 将监听的文件描述符相关的检测信息添加到epoll实例中
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

    struct epoll_event epevs[1024];

    while(1) {

        int ret = epoll_wait(epfd, epevs, 1024, -1);
        if(ret == -1) {
            perror("epoll_wait");
            exit(-1);
        }

        printf("ret = %d\n", ret);

        for(int i = 0; i < ret; i++) {

            int curfd = epevs[i].data.fd;

            if(curfd == lfd) {
                // 监听的文件描述符有数据达到,有客户端连接
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
                printf("有客户端连接\n");
                epev.events = EPOLLIN;
                epev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
            } else {
                if(epevs[i].events & EPOLLOUT) {
                    continue;
                }   
                 printf("有数据到达\n");
                // 有数据到达,需要通信
                char buf[1024] = {0};
                int len = read(curfd, buf, sizeof(buf));
                if(len == -1) {
                    perror("read");
                    exit(-1);
                } else if(len == 0) {
                    printf("client closed...\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
                    close(curfd);
                } else if(len > 0) {
                    printf("数据是 %s\n", buf);
                   // write(curfd, buf, strlen(buf) + 1);
                }

            }

        }
    }

    close(lfd);
    close(epfd);
    return 0;
}

client

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

    // 创建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(9999);

    // 连接服务器
    int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }


    while(1) {
        char sendBuf[1024] = {0};
        read(0,sendBuf,sizeof(sendBuf));
        write(fd, sendBuf, strlen(sendBuf) + 1);
        
    
        sleep(1);
    }

    close(fd);

    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值