Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5360
  • 博文数量: 2
  • 博客积分: 65
  • 博客等级: 民兵
  • 技术积分: 25
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-16 15:32
文章分类
文章存档

2012年(1)

2011年(1)

我的朋友
最近访客

分类:

2012-02-01 16:56:38

原文地址:Socket编程(tcp) 作者:xiaolzz

《Linux C一站式学习》之Socket编程(tcp)
     这是一篇用linux c 库 的socket API实现TCP通信的文章,如果想查看原文请点
     下图是基于TCP协议的客户端/服务器程序的一般流程:



图一 TCP协议通信流程

解析:
1.TCP连接建立过程:
1)服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,
2)客户端调用 socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,
3)服务器应答一个SYN-ACK段,客户端收到后从 connect()返回,同时应答一个ACK段,
4)服务器收到后从accept()返回
5)到此完成TCP连接

2.数据传输过程:
1)连接后,TCP协议提供全双工的通信服务,但是一半的CS程序是client请求,server应答。
2)我们可以想象连接服务器与客户端的是两条管道, 这两条管道是同时打开同时关闭的。read与write函数的调用情况与操作管道的read与write函数很相像(我指的是进程阻塞和管道关闭的情况)。
3)当客户端调用close()关闭连接,就像写端关闭管道一样,服务器的read()返回0,这样服务器就知道客户端关闭连接了,
4)如果一方调用shutdown()则连接处于半关闭状态,任然可以接受对方发来的数据

下面的例子很简单,源代码可以点这里下载,我尽量在注释将源码解析清楚,最后会加上一点总结。

server.c的作用是接收客户端发送过来的字符串,然后将字符的大写返回给客户端。

/*server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
    struct sockaddr_in servaddr, chiaddr; //struct sockaddr_in结构表示IPv4地址格式
    socklen_t cliaddr_len;
    int listenfd, connfd; //文件描述符
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int i, n;

/*

socket()打开一个网络通讯端口。

对于IPv4,family参数指定为AF_INET。

对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。

如果是UDP 协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。

protocol参数的介绍从略,指定为0即可。

*/

    listenfd = socket(AF_INET, SOCK_STREAM,0);

/*

给servaddr赋值,其中htonl()与htons() 是用于网络字节序和主机字节序的转换。

首先将整个结构体清零,

然后设置地址类型为AF_INET,

网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡, 每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,

端口号为 SERV_PORT,我们定义为8000。

*/

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

/*

bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。

*/
    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

/*

listen()声明sockfd处于监听状态,并且最多允许有backlog(这里是20)个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。

*/

   listen(listenfd, 20);

    printf("Accepting connections ... \n");
    while (1)
    {
        cliaddr_len = sizeof(chiaddr);

/*

三方握手完成后,服务器调用accept()接受连接,
如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
accept()的参数listenfd是先前的监听文件描述符,

而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd

*/
        connfd = accept(listenfd, (struct sockaddr*)&chiaddr, &cliaddr_len);
        n = read(connfd, buf, MAXLINE);
        printf("receive form %s at Port %d \n",
                inet_ntop(AF_INET, &chiaddr.sin_addr, str, sizeof(str)), ntohs(chiaddr.sin_port));
        for (i=0; i<n; i++)
            buf[i] = toupper(buf[i]);
        write(connfd, buf, n);
        close(connfd);
    }

}


client.c的作用是从命令行参数中获得一个字符串发给服务器,接收服务器发回来的字符串并打印。

/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;
    char *str;

    if (argc !=2)
    {
        fputs("usage:./client message \n", stderr);
        exit(1);
           }
    str = argv[1];

/*打开一个网络通信端口*/

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

/*设置服务器地址和端口号*/

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

/*链接服务器*/
    connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

    write(sockfd, str, strlen(str));
    n = read(sockfd, buf, MAXLINE);
    printf("Response from the serve:\n");
    write (STDOUT_FILENO, buf, n);
    close(sockfd);
    return 0;
}


操作:
将源代码下载下来之后执行以下命令:
gcc -o server -g server.c
gcc -o client -g client.c
./server
(在另外一个终端cd到client执行文件所在文件夹)
./client xxx
结果应该是屏幕打印XXX并退出。

总结:最简单的TCP socket编程的一般步骤应该是这样的
服务器端:
1)调用   int socket(int family, int type, int protocol)  来打开一个网络通信端口,
2)调用   int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen) ,绑定一个固定的网络地址和端口号,其中网络地址由myaddr设定。
备注:bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。
3)调用   int listen(int sockfd, int backlog), 设置最多允许backlog个端口处于链接状态
4)调用   int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen), 接受连接。
备注:如果accept成功返回,服务器通过sockfd文件描述符与客户端通信(用read/write)

客户端:
1)调用   int socket(int family, int type, int protocol)  来打开一个网络通信端口,
2)设置好服务器地址与端口号,调用  int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen) 发起连接。
备注:如果连接成功,客户端将会用sockfd文件描述符与服务器通信。

注意:任何一方关闭sockfd文件描述符都将使得通信断开。
阅读(193) | 评论(0) | 转发(0) |
0

上一篇:博客已升级,请注意变更地址

下一篇:没有了

给主人留下些什么吧!~~