Linux下的socket编程(二)

一、TCP/IP 协议简介

1.1、TCP/IP 是基于 TCP 和 IP 这两个最初的协议之上的不同的通信协议的大的集合。

1.2、P协议 负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
1.3、TCP协议 则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
1.4、许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
1.5、一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。

二、Socket服务器和客户端的开发步骤

2.1、创建套接字
2.2、为套接字添加信息(IP地址和端口号)
2.3、监听网络连接
2.4、监听到有客户端接入,接受一个连接
2.5、数据交互
2.6、关闭套接字,断开连接

三、socket编程使用相关API

我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页 时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是 socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的。本文的主要内容如下:
1、网络中进程之间如何通信?

			2、Socket是什么?

			3、socket的基本操作

				3.1、socket()函数

				3.2、bind()函数

				3.3、listen()、connect()函数

				3.4、accept()函数

				3.5、read()、write()函数等

				3.6、close()函数

		4、socket中TCP的三次握手建立连接详解

		5、socket中TCP的四次握手释放连接详解

		6、一个例子

1、网络中进程之间如何通信?

本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:

消息传递(管道、FIFO、消息队列)

同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)

共享内存(匿名的和具名的)

远程过程调用(Solaris门和Sun RPC)

但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。

3.1、socket()函数:

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

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
demain:即协议域,又称为协议族(family)系统使用的底层协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族);AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
AF_INET IPv4因特网域
AF_INEI6 IPv6 因特网域
AF_UNIX Unix域
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
AF_UNSPEC 未指定
type:指定socket服务类型。
SOCK_STREAM(流服务,表示传输层使用TCP)
SOCK_UGRAM(数据报,表示传输层使用UDP),
SOCK_RAM:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行访问,功能强大但不方便,主要由于协议的开发。
protocol:前两个参数构成的协议集合下,再选择一个具体的协议,一般都设置为0;常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
返回值:成功返回非负整数, 它与文件描述符类似,我们把它称为套接口描述字,简称套接字。失败返回-1

**注意:**并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

3.2绑定socket()
正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

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

包含头文件<sys/socket.h>
功能:绑定一个本地地址到套接字

sockfd:socket函数返回的套接字,即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

– addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给socket的协议地址结构,这个地址结构根据地址创建socket时的地址协议族不同而不同
如ipv4对应的是:
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};

struct in_addr {
uint32_t s_addr;
};

ipv6对应的是:
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};

struct in6_addr {
unsigned char s6_addr[16];
};
Unix域对应的是:
#define UNIX_PATH_MAX 108

struct sockaddr_un {
sa_family_t sun_family;
char sun_path[UNIX_PATH_MAX];
};

– addrlen:地址长度

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

struct sockaddr{
	unisgned short    	as_family;//协议族
	char				sa_data[14];//IP+端口
};
同等替换:(常用)
struct sockaddr_in{
	sa_family_t			sin_family;//协议族
	in_port_t			sin_port;  //端口号
	struct in_addr 		sin_addr;  //IP地址结构体
	unsigned char		sin_zero[8];//填充,没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换
};

int inet_aton(const char* straddr,struct in_addr *addrp);
//把字符串的“192.168.1.1”转为网络能识别的格式。
char inet_ntoa(struct in_addr inaddr);
//把网络格式的IP地址转为字符串形式。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

3.3、监听socket

listen函数

• 包含头文件<sys/socket.h>

• 功能:将套接字用于监听进入的连接
• 原型
- int listen(int sockfd, int backlog);
• 参数
– sockfd:socket函数返回的套接字

– backlog:规定内核为此套接字排队的最大连接个数

• 返回值:成功返回0,失败返回-1;

3.4、accept函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
• 包含头文件<sys/socket.h>

• 功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。

• 原型

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

• 参数

– sockfd:服务器套接字

– addr:将返回对等方的套接字地址,用于返回客户端的协议地址

– addrlen:返回对等方的套接字地址长度

• 返回值:如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。,失败返回-1

注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

3.5、connect函数

• 包含头文件<sys/socket.h>

• 功能:建立一个连接至addr所指定的套接字

• 原型

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

• 参数

– sockfd:未连接套接字,即为客户端的socket描述字

– addr:要连接的套接字地址,服务器的socket地址

– addrlen:第二个参数addr长度
客户端通过调用connect函数来建立与TCP服务器的连接。
• 返回值:成功返回0,失败返回-1

3.6、write函数

• 包含头文件<unistd.h>

• 功能:向文件输出数据

• 原型

 -ssize_t write(int fd,const void *buf,size_t nbytes)

• 参数

– fd:数据传输对象的文件描述符

– addr:保存要传输数据的缓冲地址值

– addrlen:要传输数据的字节数

• 返回值:成功返回写入的字节数,失败返回-1

3.7、read函数

• 包含头文件<unistd.h>

• 功能:用来接收数据

• 原型

 -ssize-t read(int fd,void *buf,size_t  nbytes);

• 参数

– fd:显示数据接收对象的文件描述符

– buf:要保存接收数据的缓冲地址值

– nbytes:要接收数据的最大字节数

• 返回值:成功返回接收的字节数,失败返回-1

3.8、close函数

• 包含头文件<unistd.h>

• 功能: 关闭文件或套接字

• 原型

 -int close(int fd);

• 参数

– fd:需要关闭的文件或套接字的文件描述符

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

3.9、fgets函数

• 包含头文件<stdio.h>

• 功能: 从文件流中读取一行或指定个数的字符

• 原型

 -char *fgets(char *string,int size,FILE *stream);

• 参数

– string:字符数组,用来保存读取到的字符

– size:要读取的字符的个数,如果该行字符数大于size-1,则读到size-1个字符时结束,并在最后补充‘\0’;如果该行字符数小于等于size-1,则读取所有字符,并在最后补充‘\0’。

– stream :文件流指针

• 返回值:读取成功,返回读到的字符串,即string;失败返回NULL。

3.10、fputs函数

• 包含头文件<stdio.h>

• 功能:将指定的字符串写入到文件流中

• 原型

 -int fputs(char *string,FILE *stream);

• 参数

– string:为将要写入的字符串

– stream:文件流指针

• 返回值:成功返回非负数,失败返回EOF

3.11、handler函数

• 包含头文件<stdio.h>

• 功能:特定事件发生时,操作系统向进程发送信息

• 原型

 -void handler(int sig)

• 返回值:参数类型为int型,返回void型函数

3.12、signal函数

• 包含头文件<signal.h>

• 功能:获取系统产生各种信号并对此信号调用用户自己定义

• 原型

 -void (* signal(int signo,void (*func)(int)))(int)

• 参数

– 第一个参数:指定信号的值

– 第二个参数:一个函数指针,用于指定针对信号的处理函数的函数内存空间地址

• 返回值:参数类型为int型,返回void型函数指针

3.13、fork函数

• 包含头文件<unistd.h>

• 功能:把Linux 变换为多任务系统的基础

• 原型

 -pid_t fork(void)

• 返回值:成功时返回进程ID,失败时返回-1

3.14、kill函数

• 包含头文件<signal.h>

• 功能:发送指定的信号到相应进程

• 原型

 -int kill(pid_t,int sig)

• 参数

– pid_t:参数进程标识号

– sig:进程号要传递信号值

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

3.15、通信
TCP数据读写

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

recv读取sockfd上的数据,buf和len分别指定读缓冲区的位置和大小,flags通常为0。成功时返回实际读取到的数据长度,它可能小于期望的长度,因此需要多次调用recv;出错时返回-1并设置errno;

send往sockfd上写入数据,buf和len分别指定写缓冲区的位置和大小。成功时返回实际写入的数据长度,失败则返回-1并设置errno;

4.下面给出实现的一个实例

模拟ftp服务器
在这里插入图片描述
服务器端代码如下:

#include "config.h"

int getCmdType(char* cmd)
{
    if(!strcmp("ls",cmd))        return LS;
    if(!strcmp("quit",cmd))      return QUIT;
    if(!strcmp("pwd",cmd))       return PWD;
    if(strstr(cmd,"cd")!=NULL)   return CD;
    if(strstr(cmd,"get")!=NULL)  return GET;
    if(strstr(cmd,"put")!=NULL)  return PUT;

    return 100;
}


char* getDesDir(char* cmsg)
{
    char* p;
    p=strtok(cmsg," ");
    p=strtok(NULL," ");

    return p;
}


void* msgHandler(struct Msg msg,int fd)//客户端的接入
{
    char dataBuf[1024]={0};
    char* file=NULL;
    int fdfile;

    printf("cmd:%s\n",msg.data);
    int ret=getCmdType(msg.data);

    switch(ret)
    {
        case LS:
        case PWD:
                msg.type=0;
                FILE* r=popen(msg.data,"r");
                fread(msg.data,sizeof(msg.data),1,r);
                write(fd,&msg,sizeof(msg));

                break;
        case CD:
                msg.type=1;
                char* dir=getDesDir(msg.data);
                printf("dir:%s\n",dir);
                chdir(dir);
                break;

        case GET:
                file=getDesDir(msg.data);
                if(access(file,F_OK)==-1)
                {
                    strcpy(msg.data,"NO This File!");
                    write(fd,&msg,sizeof(msg));
                }
                else
                {
                    msg.type=DOFILE;
                    fdfile=open(file,O_RDWR);
                    read(fdfile,dataBuf,sizeof(dataBuf));
                    close(fdfile);

                    strcpy(msg.data,dataBuf);
                    write(fd,&msg,sizeof(msg));
                }
                break;
        case PUT:
                fdfile=open(getDesDir(msg.data),O_RDWR|O_CREAT,0666);
                write(fdfile,msg.secondBuf,strlen(msg.secondBuf));
                close(fdfile);
                break;
        case QUIT:
                printf("client qiut!\n");
                exit(-1);    

    }

} 


int main(int argc,char** argv)
{
    int s_fd;
    int c_fd;
    int n_read;
    char readBuf[128];


    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;
    struct Msg msg;

    if(argc!=3){
        printf("param is not good!\n");
        exit(-1);
    }

    memset(&s_addr,0,sizeof(struct sockaddr_in));
    memset(&c_addr,0,sizeof(struct sockaddr_in));

    //1、socket
    s_fd=socket(AF_INET,SOCK_STREAM,0);
    if(s_fd==-1)
    {
        perror("socket");
        exit(-1);
    }

    s_addr.sin_family=AF_INET;
    s_addr.sin_port=htons(atoi(argv[2]));
    inet_aton(argv[1],&s_addr.sin_addr);

    //bind

    bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));

    //listen
    listen(s_fd,10);
    //accept

    int clen=sizeof(struct sockaddr_in);
    while(1)
    {
        c_fd=accept(s_fd,(struct sockaddr*)&c_addr,&clen);
        if(c_fd==-1)
        {
            perror("accept");
        }
        printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
        //1、当有客户端接入时创建一个子进程,去创建连接
        //2、exec族函数;
        if(fork()==0)//一个子进程负责一个连接通道
        {
            while(1)
            {
                memset(msg.data,0,sizeof(msg.data));
                n_read=read(c_fd,&msg,sizeof(msg));
                if(n_read==0)
                {
                    printf("client out!\n");
                    break;
                }
                else if(n_read>0)
                {
                    msgHandler(msg,c_fd);
                }
            }
        }
    }
    close(c_fd);
    close(s_fd);
    return 0;
}


客户端代码:

#include "config.h"

char* getdir(char* cmd)
{
    char* p;
    p=strtok(cmd," ");
    p=strtok(NULL," ");

    return p;
}

int getCmdType(char* cmd)
{
    if(strstr(cmd,"lcd"))   return  LCD;

    if(!strcmp("quit",cmd)) return  QUIT;
    if(!strcmp("ls",cmd))   return  LS;
    if(!strcmp("lls",cmd))  return  LLS;
    if(!strcmp("pwd",cmd))  return  LS;

    if(strstr(cmd,"cd"))    return  CD;
    if(strstr(cmd,"get"))   return  GET;
    if(strstr(cmd,"put"))   return  PUT;

    return -1;
}

int cmdHandler(struct Msg msg,int fd)
{
    char buf[32];
    char* dir=NULL;
    int filefd;
    int ret=getCmdType(msg.data);

    switch(ret)
    {
        case LS:
        case CD:
        case PWD:
                msg.type=0;
                write(fd,&msg,sizeof(msg));
                break;
        case GET:
                msg.type=2;
                write(fd,&msg,sizeof(msg));
                break;
        case PUT:
                strcpy(buf,msg.data);
                dir=getdir(buf);

                if(access(dir,F_OK)==-1)
                {
                    printf("%s not exsit\n",dir);
                }
                else{
                    filefd=open(dir,O_RDWR);
                    read(filefd,msg.secondBuf,sizeof(msg.secondBuf));
                    close(filefd);

                    write(fd,&msg,sizeof(msg));
                }
                break;
        case LLS:
                system("ls");
                break;
        case LCD:
                dir=getdir(msg.data);
                chdir(dir);
                break;
        case QUIT:
                strcpy(msg.data,"quit");
                write(fd,&msg,sizeof(msg));
                close(fd);
                exit(-1);
    }
    return ret;

}


void handlerServerMessage(int c_fd,struct Msg msg)
{
    int n_read;
    struct Msg msgget;
    int newfilefd;

    n_read=read(c_fd,&msgget,sizeof(msgget));

    if(n_read==0)
    {
        printf("server is out quit\n");
        exit(-1);
    }
    else if(msgget.type==DOFILE)
    {
        char* p=getdir(msg.data);
        newfilefd=open(p,O_RDWR|O_CREAT,06000);
        write(newfilefd,msgget.data,strlen(msgget.data));
        putchar('>');
        fflush(stdout);
    }
    else
    {
        printf("----------------------------------------\n");
        printf("\n%s\n",msgget.data);
        printf("----------------------------------------\n");

        putchar('>');
        fflush(stdout);
    }

}


int main(int argc, char **argv)
{
    int c_fd;
    struct sockaddr_in c_addr;//internet环境下套接字的地址形式
    struct Msg msg;

    memset(&c_addr,0,sizeof(struct sockaddr_in));

    if(argc!=3)
    {
        printf("param is not good!\n");
        exit(-1);
    }

    c_fd=socket(AF_INET,SOCK_STREAM,0);
    if(c_fd==-1)
    {
        perror("socket");
        exit(-1);
    }

    c_addr.sin_family=AF_INET;
    c_addr.sin_port=htons(atoi(argv[2]));
    inet_aton(argv[1],&c_addr.sin_addr);

    //connect

    if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1)
    {
        perror("connect");
        exit(-1);
    }
    printf("connect ...\n");
    int mark=0;
    while(1)
    {
        memset(msg.data,0,sizeof(msg.data));
        if(mark==0) printf(">");

        gets(msg.data);//获取用户输入

        if(strlen(msg.data)==0)
        {
            if(mark==0)
            {
                printf(">");
            }
            continue;
        }
        mark=1;
        int ret=cmdHandler(msg,c_fd);

        if(ret>IFGO)
        {
            putchar('>');
            fflush(stdout);
            continue;
        }
        if(ret==-1)
        {
            printf("command net\n");
            printf(">");
            fflush(stdout);
            continue;
        }
        handlerServerMessage(c_fd,msg);
    }

    return 0;
}

config.h

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



#define LS      0
#define GET     1
#define PWD     2

#define IFGO    3

#define LCD     4
#define LLS     5
#define CD      6
#define PUT     7


#define QUIT    8
#define DOFILE  9


struct Msg
{
    int type;
    char data[1024];
    char secondBuf[128];
};








  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值