网络编程基础知识

        网络编程就是编写程序使两台连网的计算机相互交换数据。而socket就是连接两台计算机的纽带,首先了解一些socket相关函数。

SOCKET

        创建套接字所用的socket函数

#include <sys/socket.h>

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

成功时候返回socket文件描述符,失败时返回-1

       domin是指套接字中使用的协议簇信息,如PF_INET/AF_INET(IPV4互联网协议簇),PF_INET6/AF_INET6(IPV6互联网协议簇)

       type指套接字数据传输类型信息,有面向连接的套接字(SOCK_STREAM)和面向消息的套接字(SOCK_DGRAM),面向连接的套接字可以理解成TCP协议,特点有:传输过程中数据不会消失,按序传输数据,传输的数据不存在数据边界(意思就是接收数据和传输数据次数可以不同),因为收发数据的套接字内部有缓冲,因此服务器和客户端调用的read和write并不马上调用,有可能分多次调用。面向消息的套接字可以当作udp,特点就是快速传输,传输数据可能丢失,有数据边界,限制每次传输数据大小。

       protocol指计算机间通信中使用的协议信息。一般都可以为0,如果同一协议簇中存在多个数据传输方式相同的协议,则才用第三个参数。

BIND

        创建完套接字后就要描述socket一些特征。bind函数作用是把一个本地协议赋予一个套接字。如果一个TCP客户或者服务器不用bind绑定一个端口,则调用connect或listen时,内核就要为相应的套接字选择一个临时端口(一般TCP客户端可这样做,TCP服务器不可,因为服务器的端口是大家熟知的)

        对于TCP客户端而言,通常不把IP地址绑定到套接字上,当连接套接字时(connect),内核将根据所用外出网络接口选择源IP地址。如果让内核来为套接字选择一个临时端口,那么需要用函数getsockname来返回协议地址

        对于TCP服务器而言,如果套接字没有捆绑IP地址,内个就会把客户发送的SYN目的IP地址作为服务器的源IP地址。IPV4的INADDR_ANY就是通配地址

#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen)
//注意,绑定的是自己的地址myaddr(服务器的地址)
//成功时返回0,失败时返回1

        sockfd是socket文件描述符,结构体sockaddr_in如下,最后一个参数就是地址长度。

struct sockaddr_in
{
    sa_family_t      sin_family;   //地址簇,取值AF_INET(IPV4),AF_INET6(IPV6)
    unit16_t         sin_port;     //16位TCP/UDP端口号,以网络字节序保存
    struct in_addr   sin_addr;     //32位IP地址,以网络字节序保存
    char             sin_zero[8];  //不使用,但一般初始化为0

}


struct in_addr
{
    In_addr_t    s_addr;   //32位IP地址

}

网络字节序和主机字节序

         因为不同CPU内存保存数据方式有两种,分别是大端序(高位字节在低位地址,低位字节在高位地址,因为规定在传输数据中用大端序,所以也称网络字节序)和小端序(高位字节在高位地址,低位字节在低位地址,现代PC中大多采用小端字节序,所以也称为主机字节序),在网络传输数据时为了统一方式,约定用网络字节序(大端序),以下几个函数可以实现。

//short是16位,一般用于port,long一般用于address
unsigned short htons(unsigned short);   //host to network把short类型数据
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);


//还有两个可以把字符串形式的IP地址转换成32位整数型数据,成功调用后返回32位大端整数

in_addr_t inet_addr(const char * string);

int inet_aton(const char * string, struct in_addr * addr);

//iner_aton可以将转换后的IP地址信息带入sockaddr_in结构体中声明in_addr结构体变量。

char * inet_ntoa(struct in_addr adr);将网络字节序整数型IP地址转换成字符串


//以下两个函数ipv4和ipv6通用,p代表表达地址的表达格式通常是ASCII字符,n代表数值,数值格式是存放在套接字地址结构里面的二进制值
//第一个函数将strptr指针所指的字符串转换到addrptr指针存放二进制结果
int inet_pton(int family, const char *strptr, void *addrptr);
//这个函数将数值格式addrptr转换成表达格式strptr,若成功则指向结果的指针
const char *inet_ntop(int family,const void *addrptr, char *strptr, size_t len);

LISTEN

        调用listen函数进入等待连接请求状态,仅用于TCP服务器用,他做两件事情

  1. 当socket函数创建套接字时,默认为主动套接字(主动套接字就是要主动调用connect发起连接的套接字),listen函数将其变成被动套接字,指示内核应该接受指向该套接字的连接请求,将套接字CLOSED状态变成LISTEN状态
  2. listen第二个参数指定了内核应该为相应套接字排队的最大连接个数。内核给任意一个套接字维护两个队列:未完成连接队列(未完成三次握手)和已完成连接队列(已完成三次握手)。当进程调用accept时,已完成连接队列中的队头将返回给进程,如果队列为空,那么进程将睡眠,直到TCP在该队列中放入一项才唤醒。

 

#include <sys/socket.h>

int listen (int sock, int backlog);   //sock位希望进入等待连接请求状态的套接字文字描述
                                      //符,backlog为连接请求等待队列长度

ACCEPT

        用于TCP服务器调用,接收客户端连接请求,从已完成连接队列队头返回下一个已完成连接,函数返回的已连接套接字是自动创建的,并自动与发起连接请求的客户端建立连接。如果accept返回成功,那么其返回值是由内核自动生成的已连接套接字(称第一个由socket创建的参数为监听套接字)。一个服务器通常吃创建一个监听套接字,其在服务器的生命周期中一直存在,而内核为每个由服务器进程接受的客户连接创建一个已连接套接字,当服务器完成对客户的服务时,相应的已连接套接字即可关闭。

#include <sys/socket.h>

int accept(int sock,struct sockaddr *cliaddr, socklen_t *addrlen);
//参数cliaddr和addrlen用来返回已连接的对端进程的协议地址

   ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

CONNECT

对于客户端的套接字比较简单,首先也是调用socket函数创建套接字,然后直接调用connect函数向服务器端发送连接请求。

connect函数用来建立TCP客户端与服务器的连接。如果是TCP套接字,调用connect函数将激发TCP三次握手过程,有以下三种出错情况。如出现调用失败,则需要close当前套接字描述符并重新调用socket

  1. 若TCP客户端没有收到SYN分节的响应,则返回ETIMEDOUT错误
  2. 若对客户端的SYN响应是RST(表示复位),则表明服务器主机在指定端口上没有进程在等待与之连接(例如服务器进程没有运行),返回ECONNREFUSED错误
  3. 若客户端发出的SYN在某个路由器上发生ICMP错误(例如目的地不可达),则返回ENETUNREACH

根据TCP状态转移,connect函数导致当前套接字从CLOSED状态变成SYN_SENT状态,若成功则再次转移到ESTABLISHED。参考:TCP详解

#include <sys/socket.h>
//第二个和第三个参数分别是一个指向套接字地址结构的指针和该结构的大小
int connect (int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);

 

------------------------------------------------------------------------------------------------------------------------------------------------------

例子:最简单的服务器客户端。

客户端:

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

int main(int argc,char* argv[])
{
        int sock;
        struct sockaddr_in serv_addr;
        char message[30];
        int str_len;

        if(argc!=3)
        {
                printf("Usage:%s<IP><port>\n",argv[0]);
                exit(1);
        }

        sock=socket(PF_INET,SOCK_STREAM,0);
        if(sock==-1) printf("socket error");

        memset(&serv_addr,0,sizeof(serv_addr));
        serv_addr.sin_family=AF_INET;
        serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
        serv_addr.sin_port=htons(atoi(argv[2]));

        if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
                printf("connect error");
        str_len=read(sock,message,sizeof(message)-1);
        if(str_len==-1) printf("read error");

        printf("Message from server %s\n",message);
        close(sock);
        return 0;

}

服务器:

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

int main(int argc, char *argv[])
{
        int serv_sock;
        int clnt_sock;

        struct sockaddr_in serv_addr;
        struct sockaddr_in clnt_addr;
        socklen_t clnt_addr_size;

        char message[]="HELLO WORLD";

        if(argc!=2)
        {
                printf("Usage:%s<port>\n",argv[0]);
                exit(1);
        }
        serv_sock=socket(PF_INET,SOCK_STREAM,0);
        if(serv_sock==-1)
                printf("socket error");

        memset(&serv_addr,0,sizeof(serv_addr));
        serv_addr.sin_family=AF_INET;
        serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
        serv_addr.sin_port=htons(atoi(argv[1]));

        if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)
                printf("bind error");

        if(listen(serv_sock,5)==-1)
                printf("listen error");

        clnt_addr_size=sizeof(clnt_addr);

        clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);
        if(clnt_sock==-1)
                printf("accept error");

        write(clnt_sock,message,sizeof(message));

        close(clnt_sock);close(serv_sock);
        return 0;

}

参考:《TCP/IP网络编程》《UNP》《Linux高性能服务器编程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值