Linux网络编程——tcp的应用教程

初识网络编程的用途:

以前我们讲的进程间的通信,是实现一台计算机上不同进程之间的通信。现在我们可以通过网络编程实现不同机器,甚至不同操作系统之间的通信。

知识预备:

计算机网络基础知识

注意:

这里不介绍关于网络的具体知识,关于tcp/ip的预备知识可参考谢希仁的《计算机网络》。

概要:

本文将通过一个实例实现linux与windows之间的文件传送,要求使用tcp协议。

网络编程常用函数:

以下函数的介绍是本人结合实例给出的定义,难免有疏漏,望谅解。

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

描述:client端连接到server端,如果成功返回0,失败返回-1;
第一个参数指客户端的socekt,client_st
第二个参数指server端ip地址结构
第三个参数指ip地址长度

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

描述:创建socket,如果创建成功,返回socket描述符
第一个参数一般填 AF_INET 意思是 IPv4 Internet protocols
第二个参数如果要用tcp协议 填SOCK_STREAM ;如果要用UDP协议填SOCK_DGRAM

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

描述:bind将socket与本地(server)IP地址绑定
第一个参数sockfd指刚创建还未与ip地址绑定的socket
第二个参数addr指ip地址结构
第三个参数addrlen指addr的数据长度

int listen(int sockfd, int backlog);

描述:server端listen 是否有来自client端的连接到达,如果有成功连接则返回0;如果失败错误则返回-1
第一个参数是socket描述符
第二个参数指最多能listen来自客户端的个数

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

描述:如果有client的连接到达,accept返回一个client_st,这个socekt是新创建,原来处在listening state的socket不受影响
第一个参数指listenng socket(listen_st)
第二个参数指client_addr,这是通过指针返回的client端的ip结构地址
第三个参数是client端地址的长度,这也是一个指针返回的值

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

描述:socket发送数据,前提是socket处在连接状态,如果成功发送,返回发送的字节数;失败则返回-1;
第一个参数:socket
第四个参数:一般设为0

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

描述:socket接收数据,前提是socket处在连接状态,如果成功接收,返回接收的字节数;失败返回-1;
第一个参数:socket
第四个参数:一般设为0

实例实现

代码中有详细的注释

/*
 ============================================================================
 Name        : server.c
 Author      : Allen
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include"pub.h"

int main(int arg,char *args[])
{
    if(arg<2)
    {
        printf("usage:server port\n");
        return EXIT_FAILURE;
    }
    int port=atoi(args[1]);
    if (port == 0)
    {
        printf("port %d is invalid\n", port);
        return EXIT_FAILURE;
    }

    printf("recv begin\n");
    if((recv_work(port))==1)
        printf("recv success\n");
    else
        printf("recv fail\n");

    return EXIT_SUCCESS;
}
/*
 * client.c
 *
 *  Created on: 2016年10月20日
 *      Author: Administrator
 */
#include <stdio.h>
#include <stdlib.h>

#include"pub.h"

int main(int arg, char*args[])
{
    if (arg < 4)
    {
        printf("usage:client hostname port filepath");
        return EXIT_FAILURE;
    }

    int port = atoi(args[2]);
    if (port == 0) //如果端口号为0,main函数退出
    {
        printf("port %d is invalid\n", port);
        return EXIT_FAILURE;
    }

    printf("%s send begin\n", args[3]);
    if ((send_work(args[1], port, args[3])) == 1)
        printf("send success\n");
    else
        printf("send_work failed\n");

    return EXIT_SUCCESS;
}

/*
 * pub.h
 *
 *  Created on: 2016年10月20日
 *      Author: Administrator
 */
#ifndef __PUB_H
#define __PUB_H
int send_work(const char*hostname,int port,const char*filename);
int recv_work(int port);
#endif
/*
 * pub.c
 *
 *  Created on: 2016年10月20日
 *      Author: Administrator
 */
//如果是windows操作系统就包含WinSock2.h头文件
#ifdef WIN
#include <WinSock2.h>
//如果不是
#else
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#define SOCKET int //因为windows下的socket是特殊的SOCKET类型
#endif

#include <stdio.h>
#include "pub.h"

#define SOCKET int

#include <stdio.h>

#define MAXBUFSIZE 262144
//getfilename将要要发送的文件路径解析出其中的文件名称C:\myfiletransfer\src\TCP.PNG
//就解析出TCP.PNG
void getfilename(const char*filename, char *name)
{
    int len = strlen(filename);
    int i = 0;
    for (i = (len - 1); i >= 0; i--)
    {
        if ((filename[i] == '/') || (filename[i] == '\\'))
        {
            break;
        }
    }
    strcpy(name, &filename[i + 1]);
    return;
}
//windows下使用要socket要进行初始化,linux则不需要
SOCKET socket_init()
{
    //如果是windows,执行如下代码
#ifdef WIN
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(1, 1);
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0)
    {
        return -1;
    }

    if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
    {
        WSACleanup();
        return -1;
    }
#endif
    return 0;
}

//client端connect
SOCKET socket_connect(const char*hostname, int port)//连接到指定主机和端口号
{
    if (socket_init() == -1)
        return 0;

    SOCKET client_st = socket(AF_INET, SOCK_STREAM, 0);  //初始化客户端socket为tcp
    struct sockaddr_in addr;  //目的主机地址
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);//指定port为要连接的端口号
    addr.sin_addr.s_addr = inet_addr(hostname);//指定hostname为要连接的IP地址
    if ((connect(client_st, (struct sockaddr*) &addr, sizeof(addr)) == -1))
    {
        printf("connect failed %s\n", strerror(errno));
        return 0;
    }
    return client_st;//连接成功,则返回client端的socket描述符
}

//server端bind listen
SOCKET socket_create(int port)//在指定的端口号建立server端的socket
{
    if (socket_init() == -1)
        return 0;

    SOCKET st = socket(AF_INET, SOCK_STREAM, 0);//建立TCP socket
    if (st == 0)
    {
        printf("socket failed %s\n", strerror(errno));
        return 0;
    }

#ifdef WIN
    const char on = 0;
#else
    int on = 0;
#endif

    if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        printf("setsockopt failed %s\n", strerror(errno));
        return 0;
    }

    struct sockaddr_in addr;//定义一个IP地址结构
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;//将addr结构的属性定位为TCP/IP地址
    addr.sin_port = htons(port);//将本地字节顺序转化为网络字节顺序。
    addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY代表这个server上所有的地址
    //server端socket 与 server上的ip地址绑定
    if ((bind(st, (const struct sockaddr *) &addr, sizeof(addr))) == -1)
    {
        printf("bind failed %s\n", strerror(errno));
        return 0;
    }
    if ((listen(st, 20)) == -1)
    {
        printf("listen failed %s\n", strerror(errno));
        return 0;
    }
    printf("listen %d success\n", port);

    return st;  //server端的socket建立成功,返回server端的socket描述符
}

//server端accept  返回连接到server的client的socket描述符
SOCKET socket_accept(SOCKET listen_st)//server端socket开始accept的函数
{
    struct sockaddr_in client_addr;

#ifdef WIN
    int len = 0;
#else
    unsigned int len = 1;
#endif

    len = sizeof(client_addr);
    memset(&client_addr, 0, sizeof(client_addr));

    SOCKET client_st = accept(listen_st, (struct sockaddr *) &client_addr,
            &len);//accept阻塞,直到有client连接到server才返回
    if (client_st == -1)
    {
        printf("accept failed %s\n", strerror(errno));
        return 0;
    } else
    {
        printf("accept by %s\n", inet_ntoa(client_addr.sin_addr));
        return client_st;//有client连接到server,则返回client的socket描述符
    }
}

//client端向server端发送文件  向指定的ip 指定的端口号发送指定的文件
int send_work(const char*hostname, int port, const char*filename)
{
    SOCKET st = socket_connect(hostname, port);//连接到hostname指定的的ip地址和port指定的端口号
    if (st == 0)
    {
        return 0;
    }
    FILE*fp = fopen(filename, "rb");//以只读的方式打开filename rb是windows用于读取二进制文件,linux下与r相同
    if (fp == NULL)
    {
        printf("fopen failed %s\n", strerror(errno));
        return 0;
    }

    char *buf = malloc(MAXBUFSIZE);//申请一块缓存区,存放接收到的文件内容
    memset(buf, 0, MAXBUFSIZE);
    getfilename(filename, buf);  //从完整的路径名解析出的文件名称

    int rc = send(st, buf, strlen(buf), 0);//client端第一次给server端发送的是解析得到的文件名称
    if (rc <= 0)
    {
        if (rc < 0)
            printf("send failed %s\n", strerror(errno));
        else
            printf("socket disconnect\n");
    } else
    {
        memset(buf, 0, MAXBUFSIZE);
        rc = recv(st, buf, MAXBUFSIZE, 0);//接受来自server端的回复
        if (rc <= 0)
        {
            if (rc < 0)
                printf("recv failed %s\n", strerror(errno));
            else
                printf("socket disconnect");
        } else
        {
            if (strncmp(buf, "OK", 2) == 0)//如果server端给client回复了OK,代表server认可,client可以发送数据
            {
                while (1)
                {
                    memset(buf, 0, MAXBUFSIZE);
                    rc = fread(buf, 1, MAXBUFSIZE, fp);//循环读取文件,直到读到文件末尾,循环break
                    if (rc <= 0)
                    {
                        if (rc < 0)
                            printf("fread failed %s\n", strerror(errno));
                        break;
                    } else
                    {
                        rc=send(st, buf, rc, 0);//将从文件读取的数据,通过socket发送到server端,其中rc为读取到的数据的大小
                        if (rc <= 0)
                        {
                            if (rc < 0)
                                printf("send failed %s\n", strerror(errno));
                            else
                                printf("socket disconnect");
                            break;
                        }
                    }
                }
            }
        }
    }
    fclose(fp);//关闭文件
    free(buf);//释放buf

#ifdef WIN
    closesocket(st);
    WSACleanup();
#else
    close(st);
#endif
    return 1;
}

int recv_work(int port)//server端socket在port指定的端口listen,接受来自client发送的文件
{
    SOCKET listen_st = socket_create(port);//建立server端socket,在port指定的端口号listen
    if (listen_st == 0)
    {
        return 0;
    }
    SOCKET st = socket_accept(listen_st);//如果有client连接到达,socket_accept函数返回client的socket描述符
    if (st == 0)
    {
        return 0;
    }

    char *buf = malloc(MAXBUFSIZE);
    if (buf == NULL)
    {
        return 0;
    }

    FILE *fp = NULL;
    memset(buf, 0, MAXBUFSIZE);
    int rc = recv(st, buf, MAXBUFSIZE, 0);//接受client端第一次发送的数据,即文件名称
    if (rc <= 0)
    {
        if (rc < 0)
        {
            printf("recv failed %s\n", strerror(errno));
        } else
        {
            printf("socket disconnect\n");
        }
    } else
    {
        printf("recving %s\n", buf);
        fp = fopen(buf, "wb");//以只写的方式打开buf指定文件名,若不存在,新建一个名为buf指定的文件名
        if (fp == NULL)
        {
            printf("fopen failed %s\n", strerror(errno));
        } else
        {
            memset(buf, 0, MAXBUFSIZE);
            strcpy(buf, "OK");
            rc = send(st, buf, strlen(buf), 0);//如果已接收到client端第一次发来的文件名,则server端要给client端发送OK去人
            if (rc <= 0)
            {
                if (rc < 0)
                    printf("send failed %s\n", strerror(errno));
                else
                    printf("socket disconnect");
            }

            while (1)
            {
                memset(buf, 0, MAXBUFSIZE);
                rc = recv(st, buf, MAXBUFSIZE, 0);//循环开始接受数据,数据为文件内容
                if (rc <= 0)
                {
                    if (rc < 0)
                        printf("recv failed %s\n", strerror(errno));
                    else
                        printf("socket disconnect\n");
                    break;
                } else
                {
                    fwrite(buf, 1, rc, fp);//将接受到的数据,写入fp指定的文件,一条记录为1个字节,一次写rc条记录
                }
            }
        }
    }

    if (fp)
        fclose(fp);
    free(buf);

#ifdef WIN
    closesocket(st);
    closesocket(listen_st);
    WSACleanup();
#else
    close(st);
    close(listen_st);
#endif

    return 1;
}

下面分别提供了linux下的makefile以及windows下的makefile

.SUFFIXES: .c .o

CC=gcc

SRCS1=client.c\
    pub.c
SRCS2=server.c\
    pub.c

OBJS1=$(SRCS1:.c=.o)
OBJS2=$(SRCS2:.c=.o)
EXEC1=client
EXEC2=server

all: $(OBJS1) $(OBJS2)
    $(CC) -o $(EXEC1) $(OBJS1)
    $(CC) -o $(EXEC2) $(OBJS2)
    @echo '-------------ok--------------'

.c.o: 
    $(CC) -Wall -g -o $@ -c $<

clean:
    rm -f $(OBJS1)
    rm -f $(OBJS2)
    rm -f core*

.SUFFIXES: .c .o

CC=gcc
SERVERSRCS=server.c\
            pub.c
CLIENTSRCS=client.c\
            pub.c

SERVEROBJS=$(SERVERSRCS:.c=.o)
CLIENTOBJS=$(CLIENTSRCS:.c=.o)
SERVEREXEC=server.exe
CLIENTEXEC=client.exe

all:$(SERVEROBJS) $(CLIENTOBJS)
    $(CC) -static -o $(SERVEREXEC) $(SERVEROBJS) -lWs2_32 
    $(CC) -static -o $(CLIENTEXEC) $(CLIENTOBJS) -lWs2_32 
    @echo '-------------ok--------------'

.c.o:
    $(CC) -Wall -DWIN -o $@ -c $<   

clean:
    rm -f $(SERVEROBJS)
    rm -f $(CLIENTOBJS)
    rm -f core*

tips:

gcc的小知识点:
-lWs2_32 , 代表要用Ws2_32.lib这个库
gcc编译选项,-D 代表定义一个宏,等同于在c语言当中定义 #defind WIN

TCP步骤总结:

这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值