Linux程序设计(8)socket编程

在学习Linux程序设计时,学会网络编程是必不可少的,而Linux网络编程一般通过socket(套接字)接口实现。在学习socket编程之前,我们首先要了解相关网络协议。
Linux中继续使用TCP/IP的网络层结构,即从下到上分为物理层,数据链路层,网络层,传输层 ,会话层,表示层,应用层。其中,传输层在实现通信服务时发挥了重要作用。传输层定义了TCP以及UDP协议。TCP是面向连接的通信协议,提供可靠的数据传送。TCP将源主机应用层的数据分为多个分段,将每个分段传输到网际层,网际层将数据封装为IP数据包,并发送到目的主机。
网络结构模式分为两种:客户机/服务器模式以及浏览器/服务器模式,在这里主要围绕客户机/服务器模式展开。一个完整的TCP程序设计流程如下:服务器调用socket,bind,listen函数完成初始化,接着调用accept函数阻塞等待,此时服务器处于监听端口的状态。客户端调用socket函数进行初始化,接着调用connect函数发出SYN段并阻塞,等待服务器应答。服务器应答一个SYN-ACK段,客户端收到后从connect函数返回,同时应答一个ACK段,服务器收到后从accept函数返回。此时,连接已经成功建立,可以开始传输数据了。一般由客户端发出请求,服务器处理请求,进行一问一答的方式。所以,服务器从accept函数返回后,调用read函数读取客户端的请求,如果没有请求就阻塞等待。客户端调用write函数发送请求,服务器收到请求后从read函数返回,对客户端的请求进行处理,在此期间服务器段接着调用read函数阻塞等待客户端的下一条请求。客户端,服务器之间发送,传输数据可以用send,recv函数实现。最后,数据传输完毕,客户端调用close函数关闭连接,服务器的read函数就会返回0,服务器就知道客户端关闭了连接,也调用close函数关闭连接。
套接字是一种通信机制,用于描述IP地址和端口,是一种特殊的I/O。我们常用流式套接字,即SOCK_STREAM,它提供了一个可靠的,面向连接的数据传输服务,数据无差错无重复地发送且按发送顺序接受。
int socket(int domain, int type, int protocol) #用于创建一个新的套接字
#domain又称为协议族,决定了套接字的地址类型以及在通信中必须采用对应的地址,常用套接字有AF_INET,AF_INET6…AF_INET决定了要用IPv4地址(32位)与端口号(16位)的组合。
#type指定了套接字类型,常用的套接字类型有SOCK_STREAM,SOCK_DGROM等
#protocol用于指定协议,常用的协议有:IPPROTO_TCP(TCP协议),IPPTOTO_UDP(UDP协议)等,一般置为0,表示使用默认协议。

int bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen) #将一个套接字绑定到一个地址上,使得客户端可以连接
#sockfd:上一个socket调用中获得的文件描述符
#addr指向包含绑定地址的结构的指针
#addrlen指的是地址结构的大小
一个struct sockaddr_in 结构如下:
struct sockaddr_in servaddr; //结构体定义
bzero(&servaddr, sizeof(servaddr)); //结构体清零
servaddr.sin_family = AF_INET; //设置地址类型为AF_INET
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//设置网络地址为INADDR_ANY
servaddr.sin_port = htons(6666); //设置端口号为6666

int listen(int sockfd, int backlog)
#sockfd:socket调用中获得的文件描述符
#backlog:sockfd挂起的已连接队列可以增长的最大长度

int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen) #建立客户端程序对该套接字的连接
#sockfd:socket调用中获得的文件描述符
#addr:指向连接对端的地址,即客户端地址
#addrlen指定地址结构的大小

int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen) #用于客户端建立连接,发起三次握手过程。如果连接不能立刻建立起来,connect调用会阻塞一段不确定的倒计时时间,这段倒计时时间结束后这次连接就会失败。
#sockfd:socket调用中获得的文件描述符
#addr指向服务器地址
#addrlen指定地址结构的大小

int send(int sockfd, const void * buf, size_t len, int flags) #用于发送数据
#sockfd:socket调用中获得的文件描述符
#buf:指向发送数据的指针
#len:数据的长度
#flags:一般置为0

int recv(int sockfd, const void * buf, size_t len, int flags)
#sockfd:socket调用中获得的文件描述符
#buf:要读取信息的缓冲
#len:缓冲的最大长度
#flags:一般置为0

int close(int fd) #释放系统分配给套接字的资源,关闭连接
#fd:文件描述符,在socket编程中指sockfd

在Linux中可以使用int inet_pton(int af, const char *src, void *dst)来转换IP地址。
一个C/S模型如下:

//服务器端
#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>
#include <sys/fcntl.h>
#include <netdb.h>
#include <strings.h>
#define SERV_PORT 6666
#define BUF_SIZE 5000
#define FILE_NAME_MAX_SIZE 500
void fatal(char *string) {
    printf("%s\n",string);
    exit(1);
}
int main()
{
    struct sockaddr_in servaddr, cliaddr; //定义服务器与客户端地址结构体
    socklen_t cliaddr_len; //客户端地址长度
    int listenfd, connfd, sa, fd, bytes;
    char buf[BUF_SIZE];
    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));
//监听,并设置最大连接数为20
    listen(listenfd, 20);
    printf("Accepting connections ...\n");

    while (1) {
        sa = accept(listenfd, 0, 0);
        if (sa < 0)
            fatal("accept failed");
        recv(sa, buf, BUF_SIZE, 0);

        fd = open(buf, O_RDONLY);
        if (fd < 0)
            fatal("open failed");

        while (1) {
            bytes = read(fd, buf, BUF_SIZE);
            if (bytes <= 0)
                break;
            write(sa, buf, bytes);
        }
        close(fd);
        close(sa);
    }
    return 0;
}

//客户端
#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>
#include <sys/fcntl.h>
#include <netdb.h>
#include <strings.h>
#define SERV_PORT 6666
#define BUF_SIZE 5000
#define PATH " "  //定义文件位置
#define DEST_IP "127.0.0.1" //IP地址设置为本机

void fatal(char *s) { //用于传参
    printf("%s\n",s);
    exit(1);
}
int main()
{
    int listenfd, c, bytes;
    char buf[BUF_SIZE];
    struct sockaddr_in cliaddr;
    listenfd = socket(AF_INET, SOCK_STREAM, 0); //建立socket连接
    if (listenfd < 0)
        fatal("socket");

    bzero(&cliaddr, sizeof(cliaddr));  //定义结构体
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(SERV_PORT);
    if (inet_aton(DEST_IP, &cliaddr.sin_addr) == 0) { //IP地址的转化
        fprintf(stderr, "Inet_aton erro\n");
        exit(1);
    }
    c = connect(listenfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
    if (c < 0)
        fatal("connect failed");
    send(listenfd, PATH, strlen(PATH), 0);

    while (1) {
        bytes = read(listenfd, buf, BUF_SIZE);
        if(bytes <= 0)
            exit(0);
        write(1, buf, bytes);
    }
    return 0;
}

以上程序实现了客户端与服务器建立连接,客户端请求一个文档并显示。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值