【计算机网络】网络编程---TCP套接字(一)

预备知识


socket(套接字):IP地址+端口号。可唯一标识网络通信中的一个进程。
单单只有端口号只能标识是这台主机上的唯一进程。那端口号都有哪些分类呢?
计算机端口可分为3大类:
1) 公认端口(Well Known Ports):从0到1023,它们紧密绑定于一些服务。通常这些端口的通讯明确表明了某种服 务的协议。例如:80端口实际上总是HTTP通讯。21端口是FTP服务。

 2) 注册端口(Registered Ports):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。

 3) 动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。但也有例外:SUN的RPC端口从32768开始。
 
使用
TCP与UDP段结构中端口地址都是16比特,可以有在0—65535范围内的端口号。对于这65536个端口号有以下的使用规定:
(1)端口号小于256的定义为常用端口,服务器一般都是通过常用端口号来识别的。任何TCP/IP实现所提供的服务都用1—1023之间的端口号,是由ICANN来管理的;
(2)客户端只需保证该端口号在本机上是惟一的就可以了。客户端口号因存在时间很短暂又称临时端口号;
(3)大多数TCP/IP实现给临时端口号分配1024—5000之间的端口号。大于5000的端口号是为其他服务器预留的。

TCP套接字编程

服务器端源码:

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

int startup(const char* ip,int port)  //创建监听套接字
{
    int sock=socket(AF_INET,SOCK_STREAM,0);//建立服务器端socket
    if(sock<0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_addr.s_addr=inet_addr(ip);
    server.sin_port=htons(port);
    socklen_t len=sizeof(server);
    if(bind(sock,(struct sockaddr*)&server,len)<0)// 将套接字绑定到服务器的网络地址上
    {
        perror("bind");
        exit(2);
    }
    if(listen(sock,5)<0)// 建立监听队列
    {
        perror("listen");
        exit(3);
    }
    return sock;
}


int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("Use way:%s,[IP],[port]\n",argv[0]);
        return -1;
    }
    int listen_sock=startup(argv[1],atoi(argv[2]));
    struct sockaddr_in remote;
    socklen_t relen=sizeof(remote);
    while(1)   //接收连接
    {
        int ret=accept(listen_sock,(struct sockaddr*)&remote,&relen);     // 等待客户端连接请求到达
        if(ret<0)
        {
            perror("accept");
            continue;
        }   
        printf("client's ip is %s,port is %d\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port));

        char buf[1024];
        while(1)
        {
            int s=read(ret,buf,sizeof(buf)-1);  // 接收客户端数据
            if(s>0)
            {
                buf[s]='\0';
                printf("client#:%s\n",buf);
            }
            else
            {
                printf("client if quit!\n");
                break;
            }
        }
        close(ret);
    }
    return 0;
}

客户端源码:

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

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("use way is %s,[ip],[port]\n",argv[0]);
        return -1;
    }
    int sock=socket(AF_INET,SOCK_STREAM,0);// 建立客户端socket
    if(sock<0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in server;  // 服务器端网络地址结构
    server.sin_family=AF_INET;
    server.sin_addr.s_addr=inet_addr(argv[1]);
    server.sin_port=htons(atoi(argv[2]));
    socklen_t len=sizeof(server);
    // 与远程服务器建立连接  
    if(connect(sock,(struct sockaddr*)&server,len)<0)
    {
        perror("connect");
        exit(2);
    }
    char buf[1024];
    while(1)
    {
        printf("send#:");
        fflush(stdout);
        int s=read(0,buf,sizeof(buf)-1);
        if(s>0)
        {
            buf[s-1]='\0';
            write(sock,buf,s);
        }
    }
    close(sock);
    return 0;
}

运行程序时,会发现一个现象,当先ctrl+c掉服务器进程时,再次启动服务器进程会出现bind: Address already in use的错误提示。
错误原因如下:
bind 试图绑定一个已经在使用的端口。该陷阱也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回 EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟(2MSL)。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。
那如果正在开发一个套接字服务器,就需要停止服务器来做一些改动,然后重启,这种情况就比较麻烦了。但是是有解决方法的:给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用。
服务器进程修改如下:

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

int startup(const char* ip,int port)  //创建监听套阶字
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_addr.s_addr=inet_addr(ip);
    server.sin_port=htons(port);
    socklen_t len=sizeof(server);
    int on=1;  
    if((setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
    {  
         perror("setsockopt");  
         exit(4);  
    }  
    if(bind(sock,(struct sockaddr*)&server,len)<0)
    {
        perror("bind");
        exit(2);
    }
    if(listen(sock,5)<0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}


int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("Use way:%s,[IP],[port]\n",argv[0]);
        return -1;
    }
    int listen_sock=startup(argv[1],atoi(argv[2]));
    struct sockaddr_in remote;
    socklen_t relen=sizeof(remote);
    while(1)   //接收连接
    {
        int ret=accept(listen_sock,(struct sockaddr*)&remote,&relen);
        if(ret<0)
        {
            perror("accept");
            continue;
        }   
        printf("client's ip is %s,port is %d\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port));

        char buf[1024];
        while(1)
        {
            int s=read(ret,buf,sizeof(buf)-1);
            if(s>0)
            {
                buf[s]='\0';
                printf("client#:%s\n",buf);
            }
            else
            {
                printf("client if quit!\n");
                break;
            }
        }
        close(ret);
    }
    return 0;
}

此时再次运行程序,先ctrl+c掉服务器进程,然后再次运行不会再报错。

你也许会发现,服务器在创建套接字之后使用了bind函数将sockfd这个⽤于⽹络通讯的⽂件描述符监听myaddr所描述的地址和端口号绑定在一起。而客户端进程并没有bind。
这是因为,客户端通过调用connect函数在socket数据结构中保存本地和远端信息,无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候打开端口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值