UNIX网络编程学习笔记

SOKET 篇(一)入门简介

什么是socket?

Socket不仅可以用于本地进程间的通信,也可以用于网络间不同主机间的通信。
Tcp/ip 协议栈已经属于内核的一部分了,应用程序是在用户空间的。

地址结构

不同主机之间通信,一定要知道相互的地址和端口号;一次连接的四个要素为:客户端ip,客户端port,服务端ip,服务端port。linux 下可以通过man 7 ip 命令查看地址结构。
ipv4地址结构:

struct sockaddr_in{
    unit8_t sin_len; // struct size 一些平台没有
    sa_family_t sin_family;   //address family: ipv4——>AF_INET
    in_port_t sin_port;    //port in network byte order 16位 
    struct in_addr sin_addr;  // iternet address
    char size_zero[8];// 保留字段
    };
utruct in_addr{
    unit32_t s_addr;
    };

ipv6将所有in字段全部改为in6即可。eg:struct sockaddr_in6;struct in6_addr sin6_addr;
通用套接字地址结构:

struct sockaddr{
    uint8_t sa_len;   //整个地址长度
    sa_family_t sa_family; // 地址簇
    char sa_data[14];//由sa_family 决定它的形式 14字节,同上面最后3项
    };

在bind等函数进行参数传递时要将套接字地址强制转换成通用套接字结构。
其中地址簇使用AF_前缀,而像后面的socket函数中domain使用PF_前缀。AF_前缀表示地址簇,PF_ 前缀表示协议簇。

字节序

  • 大端字节序:最高有效位存储在最低地址处,最低有效位存储在高地址处;
  • 小端字节序:最低有效位存储在最低地址处,最高有效位存储在高地址处;
  • 主机字节序:不同主机有不同字节序,通常我们使用的Intel 的cpu为小端字节序,而AMD的cpu是大端;
  • 网络字节序:是大端字节序。
    可以编写一个测试字节序的小函数:
int main(){
    unsigned int a = 0x12345678;
    unsigned char* b =(unsigned char*)&a;
    if(b[0] == 0x78)
        printf("little-endian");
    else
        printf("big-endian");
    return 0;
}

字节序转换

unit32_t htonl(unit32_t hostlong);
unit16_t htons(unit16_t hostshort);
unit32_t ntohl(unit32_t netlong);
unit16_t ntohs(unit16_t netshort);

上面函数名称中,h代表host,n代表network, s代表short,l代表long。
地址转换函数

#include<netinet/in.h>
#include<arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);//功能同inet_addr
in_addr_t inet_addr(const char *cp); // 点分ip地址转换成32位网络字节序整数
char *inet_ntoa(struct in_addr in);// 地址结构转换成点分式ip地址

套接字的类型

流式套接字(SOCK_STREAM):提供面向连接的,可靠数据传输服务,如对应于TCP协议;
数据报套接字(SOCK_DGRAM):提供面向连接的,非可靠数据报传输服务,如对应于UDP;
原始套接字(SOCK_RAM):将应用层数据直接跨越传输层直接对ip层进行封装。

socket函数

头文件:<sys/socket.h>
功能:创建套接字;
函数原型:

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

参数:其中domain表示协议簇,type表示套接字类型(SOCK_STREAM, SOCK_DGRAM等),protocol表示协议。通常前两项确定了protocol也就确定了,可以设置为0。
返回值:成功返回一个非负数,代表套接字描述符(文件描述符的一种,linux中一切皆文件),简称套接字。失败返回-1。

bind函数

头文件:<sys/socket.h>
功能:绑定本机地址到socket创建的套接字;
函数原型:

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

参数: sockefd指socket函数返回的套接字;addr表示要绑定的地址(通用地址结构,ipv4要强制转换);addrlen代表地址长度。
返回值:成功返回0,失败返回-1。

listen函数

头文件:<sys/socket.h>
功能:将socket产生的套接用于监听接入的连接,是将主动套接字变成了被动套接字。主动套接字通过connect发起连接,被动套接字通过accept接受连接。
函数原型:

int listen(int sockfd, int backlog);

参数: sockefd指socket函数返回的套接字;backlog规定内核为此套接字排队的最大连接数(即规定的是并发连接数)。
返回值:成功返回0,失败返回-1。
下面是直接从书上copy的图:
 tcp监听套接字维护的两个队列示意图
对于给定的监听套接字,内核会维护两个队列(他们之和不能超过backlog):
1.已由客户端发送到服务端,服务器正在等待完成tcp三次握手过程;
2.已完成三次握手连接的队列,accept接收到之后,会将接收到的已完成连接从该队列删除,以便由更多的客户端发起连接。

accept函数

头文件:<sys/socket.h>
功能:从已完成连接队列中返回第一个连接,如果已完成连接队列为空,则阻塞。
函数原型:

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

参数: sockfd:服务器套接字,addr返回对等方的套接字地址,addrlen返回对等方套接字地址长度;
返回值:成功返回非负整数,失败返回-1。(返回的是主动套接字)

connect函数

头文件:<sys/socket.h>
功能:从已完成连接队列中返回第一个连接,如果已完成连接队列为空,则阻塞。
函数原型:

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

参数: sockfd服务器套接字,addr服务器套接字地址,addrlen服务器套接字地址长度;
返回值:成功返回0,失败返回-1。

简单的回射服务器

client端:

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


#define err_exit(m)\
    do{\
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)
int main(void){
    int sockfd;
    if((sockfd = socket(PF_INET, SOCK_STREAM,0)) < 0)
        err_exit("socket");
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(5188);
    //servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //inet_aton("127.0.0.1", &servaddr.sin_addr);
    if(connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
        err_exit("connect");
    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};
    while(fgets(sendbuf,sizeof(sendbuf), stdin) != NULL){
        write(sockfd, sendbuf, strlen(sendbuf));
        read(sockfd, recvbuf, sizeof(recvbuf));
        fputs(recvbuf, stdout);
        memset(sendbuf, 0, sizeof(sendbuf));
        memset(recvbuf, 0, sizeof(recvbuf));
    }
    close(sockfd);
    return 0;
}

server端代码:

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


#define err_exit(m)\
    do{\
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)
int main(void){
    int listenfd;
    if((listenfd = socket(PF_INET, SOCK_STREAM,0)) < 0)
        err_exit("socket");
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(5188);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //inet_aton("127.0.0.1", &servaddr.sin_addr);
    if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
        err_exit("bind");
    if(listen(listenfd, SOMAXCONN) < 0)
        err_exit("listen");
    struct sockaddr_in peeraddr;
    socklen_t peerlen = sizeof(peeraddr);//如果不初始化大小,accept会出错
    int conn;
    if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
        err_exit("accept");
    char buf[1024];
    int n = 0;
    while(1){
        memset(&buf, 0, sizeof(buf));
        if((n = read(conn, buf, sizeof(buf))) < 0)
            err_exit("read");
        fputs(buf, stdout);
        write(conn, buf, n);
    }
    close(listenfd);
    close(conn);
    return 0;
}

TCP客户/服务器总体流程

tcp客户/服务器程序的套接字函数

客户端:
创建套接字sock;
指定服务器地址与端口等信息(sockaddr_in信息);
调用connect()建立连接connect;
通过sock套接字进行交互。
服务端:
创建套接字listenfd;
指定本机套接字地址;
绑定本机套接字地址,调用bind();
监听套接字listen(),并给出最大连接数(两个队列,已完成三次握手的连接队列,和未完成队列);
定义连接对等方的地址,并且初始化其地址长度。调用accept()返回一个主动套接字,和对等方的套接字地址。
通过accept返回的主动套接字进行交互。

一次连接的四个要素:客户端ip、客户端port、服务端ip、服务端port。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值