基本TCP套接口编程

基本TCP套接口编程
本文出自:http://sunsland.top263.net 作者: (2001-10-22 12:00:00)
 
概述

socket() --得到文件描述符!
bind() --我们在哪个端口?
listen() --有人给我打电话吗?
accept() --"Thank you for calling port 3490."
connect() --Hello!
send() 和 recv() --Talk to me, baby!
select()

1.socket函数

功能:指定协议类型

定义: 
#include <sys/types.h>
#include <sys/socket.h>
int socket(int family, int type, int protocol);

返回值

出错: -1 
成功: 套接口描述字 (socket file descriptor)(套接字)sockfd  

socket 函数指定了协议族(IPv4、IPv6或unix)和套接口类型(字节流、数据报或原始套接口)。但并没有指定本地协议地址或远程协议地址。

理解socket

socket使用 Unix 文件描述符 (file descriptor) 和其他程序通讯的方式。
Unix 程序在执行任何形式的 I/O 的时候,程序是在读或者写一个文件描述符。
一个文件描述符只是一个和打开的文件相关联的整数。这个文件可能是一个网络连接,FIFO,管道,终端,磁盘上的文件或者什么其他
的东西。Unix 中所有的东西是文件!因此,与 Internet 上别的程序通讯的时候,要通过文件描述符。
利用系统调用 socket()得到网络通讯的文件描述符。他返回套接口描述符 (socket descriptor),然后再通过他来调用 send() 和 recv()。
那么为什么不用一般的调用 read() 和 write() 来通过套接口通讯?
简单的答案是:可以使用一般的函数!
详细的答案是:使用 send() 和 recv() 让你更好的控制数据传输。

2.bind 函数

功能:给套接口分配一个本地协议地址

定义:

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, int addrlen);

sockfd 是调用 socket 返回的文件描述符。
my_addr 是指向数据结构 struct sockaddr 的指针,保存地址(即端口和 IP 地址) 信息。
addrlen 设置为 sizeof(struct sockaddr)。

返回: 0—成功, -1---出错

让内核自动处理地址ip和端口port
my_addr.sin_port = 0; /* choose an unused port at random */
my_addr.sin_addr.s_addr = INADDR_ANY; /* use my IP address */
bind( ) 自己选择合适的端口:将0赋给 my_addr.sin_por。
自动填上他所运行的机器的 IP 地址:my_addr.sin_addr.s_addr 设置为 INADDR_ANY。

3.listen 函数

功能:将未连接主动套接口的转换为被动套接口,指示内核接受对该套接口的连接请求.
CLOSED --? LISTEN

定义:
#include <sys/socket.h>
int listen(int sockfd, int backlog);

sockfd 是调用 socket() 返回的套接口文件描述符。
backlog 是在进入队列中允许的连接数目。

监听套接口的两个队列
未完成连接队列(incompleted connection queue): SYN_RECV
已完成连接队列(completed connection queue): ESTABLISHED

当一个客户的SYN到达时,如两队列都满的, TCP将忽略该分节且不发RST

4.ACCEPT 函数

功能:在已完成队列头返回下一个已完成的连接

定义
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, int* addrlen);

调用成功时返回:

1. cliaddr: 客户进程的协议地址和地址大小 2. 新套接口描述字(已连接套接口描述字) 

监听套接口描述字 listening socket descriptor
一个给定的服务器常常是只生成一个监听套接口, 且一直存在,直到该服务器关闭。

已连接套接口描述字connected socket descriptor
内核为每个被接受的客户连接创建了一个已连接套接口。当服务器完成某客户的服务时,关闭已连接套接口。

1024以下的端口:超级用户使用

5. connect

功能:建立与TCP服务器的连接

头文件: #include <sys/types.h>
                 #include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

//sockfd 是系统调用 socket() 返回的套接口文件描述符
serv_addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr
//addrlen 设置为 sizeof(struct sockaddr)

connect 激发 TCP的三路握手过程,服务器必须准备好接受外来的连接。这通过调用socket,bind和1isten函数来完成,称为被动打开(Passive open);客户通过调用connect进行主动打开(active opn)。这引起客户TCP发送一个SYN分节(表示同步),它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号。服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的韧始序列号。服务器以单个分节向客户发送SYN和对客户SYN的ACK。客户必须确认服务器的SYN。

connect 出错时的返回

出错原因 :未收到SYN的响应(服务器超时,75s)
返回值:ETIMEDOUT
用户端输出:Connection time out. 

出错原因 :收到RST响应(Hard error)SYN到达服务器,但该服务器的无此项端口服务
返回值:ECONNREFUSE
用户端输出:Connection refused

出错原因 :ICMP错误:不可路由(soft error)(目的地不可达)
返回值:EHOSTUNREACH
用户端输出:ENETUNREACH No route to host

6.fork 函数

功能:派生新进程 create new process

定义:
#include <sys/unistd.h>
pid_t fork (void);
在子进程中返回0,在父进程中返回子进程的进程ID
出错时返回 –1,调用一次返回两次

fork的典型应用:

1.一个进程可为自己创建一个拷贝。当一个拷贝处理一个操作时,其他的拷贝可以
执行其他的任务。这是非常典型的网络服务器。
2.一个进程想执行其他的程序,由于创建新进程的唯一方法是调用fork,进程首先调 用fork来生成一个拷贝,然后其中一个拷贝(通常为子进程)调用exec 来代替自己去执行新程序。

7.select函数

select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组, 每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他 文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成, 当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执 行了select()的进程哪一Socket或文件可读,下面具体解释:

#include <sys/select.h>
#include <sys/time.h>

int select( int maxfd,f d_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout )

该函数允许进程指示内核等待多个事件的任何一个发生,并且仅在有一个或多个事件发生或等待一段指定的时间后才唤醒。其中,描述字不局限于套接口,任何描述字都可以使用select来测试。

参数描述:
maxfd:select监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件 中的最大文件号加一。
readset:select监视的可读文件句柄集合。
writeset:select监视的可写文件句柄集合。
exceptset:select监视的异常文件句柄集合。
timeout:本次select()等待超时结束时间,可精确至百万分之一秒。

返回值:
正整数 (就绪描述字的数目);0 (超时);-1 (出错)

注意事项:

1.select一般用于服务器程序中(listen函数之后accept函数之前);

2.select一般位于for循环中,循环侦听;

3.每次select之前都要FD_ZERO;FD_SET();以及还原超时时间变量

while(1){
        FD_ZERO(&readset);
        FD_SET(server_s,&readset);
        timeout .tv_sec  = 30;
        timeout .tv_usec = 0;
        ret = select(maxfdpl+1,&rset,NULL,0,&timeout );
        if(ret==0){
            printf("Time out. Exited!/n");
            return 0;
        }
        if(FD_ISSET(server_s,&readset)){
              accept();
              read();
              write();
              ......
        }
}

基于TCP套接口的服务器客户端例子

以下是我写的一个例子,在redhat9.0上可以正常运行

1. 头文件:defines.h
服务器和客户端程序都要用到
--------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#define debug
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
typedef struct s_to_c_packet{
        char code[16];
        struct hostinfo
        {
                char hostname[32];
                char mac[6];
                char ip[4];
        } hostinfo;
} srvtoclt;

typedef struct c_to_s_packet
{
        char code[16];
} clttosrv;
--------------------------------------------------------------

2. 服务器程序:hostinfo.c
--------------------------------------------------------------
/*********************************************
function:  hostinfor server . provide local hostname,
               IP ,MAC,etc, for client
auther:    chenliang
date:       2007-10-24
*********************************************/
#include "defines.h"
int main(void){
        int server_s;
        int fd;
        int connfd;
        int ip;
        int recv_count;
        unsigned int server_port;
        struct sockaddr_in server_sockaddr;
        char hostname[32];
        struct hostent *hptr = NULL;
        char **pptr;
        char str[32];
        char msg[128];
        struct ifreq req;
        struct sockaddr_in *sin;
        struct sockaddr_in from;
        unsigned int fromlen;
        unsigned int slength;
        struct  sockaddr *phwaddr;
        srvtoclt ackpacket;
        clttosrv *check;
        #ifndef FD_SETSIZE
        #define FD_SETSIZE 256
        #endif
        fd_set rset;
        int maxfdpl;
        int ret;
        int optVal;
        struct timeval timer;

        FD_ZERO(&rset);
        timer.tv_sec  = 30;
        timer.tv_usec = 0;
        optVal = 1;

        memset(hostname, 0, 64);
        memset(&server_sockaddr, 0, sizeof server_sockaddr);
        server_port = 3721;
        fromlen = (socklen_t) sizeof(from);
        if( gethostname(hostname, 64)<0 ){
                printf("gethostname error/n");
                return -1;
        }
        strncpy( req.ifr_name, "eth0", 16 );
        sin = (struct sockaddr_in *)&req.ifr_addr;
        fd = socket( PF_INET, SOCK_RAW, IPPROTO_ICMP );
        if( fd < 0 ){
                printf("socket fd error/n");
        }
        if( ioctl( fd, SIOCGIFADDR, &req) == 0 )
        {
                ip = sin->sin_addr.s_addr;
        }else{
                perror("ioctl SIOCGIFADDR error: ");
                return -1;
        }
        phwaddr = &req.ifr_hwaddr;
        if( ioctl( fd, SIOCGIFHWADDR, &req) == 0 )
                ;
        else
                perror("ioctl SIOCGIFHWADDR error: ");

        #ifdef debug
        printf("hostname:%s/n", hostname);
        printf( "IP address:%s/n", inet_ntoa(ip) );
        printf( "MAC address:%02x:%02x:%02x:%02x:%02x:%02x/n",phwaddr->sa_data[0],phwaddr->sa_data[1],                     phwaddr->sa_data[2], phwaddr->sa_data[3], phwaddr->sa_data[4], phwaddr->sa_data[5] );
        #endif
        close(fd);

        server_sockaddr.sin_family = AF_INET;
        server_sockaddr.sin_port = htons(server_port);
        server_sockaddr.sin_addr.s_addr = INADDR_ANY;

        server_s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (server_s == -1) {
                printf("unable to create socket/n");
        }
        setsockopt(server_s, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof(optVal));
        //fcntl(server_s, F_GETFL, O_NONBLOCK);
        if( bind(server_s,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))!=0 )
        {
                printf("server create socket error:%d/n",server_s);
                exit(-1);
        };
        if( listen(server_s, 64)!=0 ){
                printf("server create socket error:%d/n",server_s);
                exit(-1);
        }

        maxfdpl = server_s;
        FD_SET(server_s,&rset);

        while(1){
                FD_ZERO(&rset);
                FD_SET(server_s,&rset);
                timer.tv_sec  = 30;
                timer.tv_usec = 0;
                memset(msg,0,sizeof(msg));
                printf("timer:%dsec/n",timer.tv_sec);
                ret = select(maxfdpl+1,&rset,NULL,0,&timer);
                if(ret==0){
                        printf("Time out. Exited!/n");
                        return 0;
                }
                if(FD_ISSET(server_s,&rset)){
                connfd = accept(server_s, (struct sockaddr *) &from, &fromlen);
                ip = from.sin_addr.s_addr;
                recv_count = read( connfd,(void *)msg,sizeof(msg) );
                check = (clttosrv *)msg;
                if((recv_count<8) ||strncmp(check->code,"get server info",15))
                {
                        printf("server packet code error/n");
                        printf("check->code: %s/n",check->code);
                        return -1;
                }
                strcpy( ackpacket.code, "server info" );
                strcpy( ackpacket.hostinfo.hostname, hostname );
                memcpy( ackpacket.hostinfo.mac, phwaddr->sa_data,6);
                memcpy( ackpacket.hostinfo.ip, &ip,4);
                slength = write( connfd,(void *)&ackpacket, sizeof(ackpacket) );
                if((slength<0)  || (slength !=sizeof(ackpacket))){
                        printf( "server send error!%d,%d/n",slength,sizeof(ackpacket));
                        exit(-1);
                }
                printf("Host %s connect in, server info transfered successfully!/n",inet_ntoa(ip));
                close(connfd);
                }
        }
        return 0;
}
-----------------------------------------------------------------------------------

3.客户端程序:getinfo.c
-----------------------------------------------------------------------------------
#include "defines.h"
int main(void){
        int socket_c;
        int ip;
        int ret;
        int server_port;
        struct sockaddr_in servaddr;
        clttosrv msg;
        unsigned int fromlen;
        unsigned int len;
        char hostinfo[128];
        srvtoclt * scpacket;
        char *pchar;

        server_port = 3721;
        strcpy(msg.code,"get server info");
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = inet_addr("172.16.3.25");
        servaddr.sin_port = htons(server_port);
#ifdef debug
        ip = servaddr.sin_addr.s_addr;
        printf( "Server IP address:%s/n", inet_ntoa(ip) );
#endif
        socket_c = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);
        if(socket_c<0){
                printf("client create socket error:%d/n", socket_c);
                return -1;
        }
        ret = connect( socket_c, (struct sockaddr *)&servaddr, sizeof(servaddr) );
        if(ret<0){
                printf("connect error:%d/n");
                return -1;
        }
        len = write(socket_c,(void *)&msg,sizeof(msg));
        if((len<0)  || (len !=sizeof(msg))){
                printf( "server send error!%d,%d/n",len,sizeof(len));
                exit(-1);
        }
        fromlen = read( socket_c,(void *)hostinfo,sizeof(hostinfo) );
        scpacket = (srvtoclt *)hostinfo;
        if(strncmp(scpacket->code,"server info",11)!=0){
                printf("Discard other info/n");
                exit(-1);
        }
        memcpy(&ip,scpacket->hostinfo.ip,4);
        pchar = (char *)&scpacket->hostinfo.mac;
        printf("server info recieved:/n");
        printf("Host name:%s/n",scpacket->hostinfo.hostname);
        printf("IP address:%s/n",inet_ntoa(ip));
        printf("MAC address:%02x:%02x:%02x:%02x:%02x:%02x/n",pchar[0],pchar[1],pchar[2],pchar[3],pchar[4],pchar[5]);
        close(socket_c);
        return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值