Linux网络编程下

前2篇文章,我写了一些网络编程的基础以及一些网络编程需要掌握的基础。

1:Linux网络编程<一>

2:Linux网络编程<二>

 

socket编程

TCP协议的流程图

收到确认消息后才会继续发送消息,否则继续等待。这样的好处是传输的数据是可靠的,此外它是有连接的传输,大多数网络传输都是用的TCP。

 

TCP协议

(1)面向连接的可靠的传输控制协议,连接的建立需要三次握手,连接的     释放需要进行四次握手才能保证连接的建立,数据的同步传输。

(2)面向字节流,会把从上层传输下来的数据当作是无结构的字节流。

(3)一对一的通信。

(4)TCP在IP协议的基础之上添加了序号机制,确认机制,超时重传机制,数据校验,从而保证传输的可靠性,同时保证不出现丢失或者是乱序。

 

TCP通信的基本步骤如下:

 

服务端:socket---bind---listen---while(1){---accept---recv---send---close---}---close

 

客户端:socket---------connect---send---recv-----------------close

 

  • 服务器端:

  • 1. 创建socket

1    sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
2    if (-1 == sock_fd)
3    {
4        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
5        exit(1);
6    }

所需要头文件
#include <sys/types.h>
#include <sys/socket.h>
函数格式
int socket(int domain, int type, int protocol);
函数功能
创建一个套接字;
domain:协议域(族),决定了套接字的地址类型,例如AF_INET决定了要用IPv4地址(32位)与端口号(16位)的组合。常见的协议族有:AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX)、AF_ROUTE等;
type:指定套接字类型SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW
protocol:指定socket所使用的传输协议编号,通常为0
返回值
若成功,返回一个套接字描述符,否则返回-1;

Socket就是一种文件描述符,和普通的打开文件一样,需要检测其返回结果。

2. 设置socket

1    memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
2    server_addr.sin_family = AF_INET;
3    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP
4    server_addr.sin_port = htons(PORT_NUMBER);

设置何种协议族,设置本机IP和端口,也就有了唯一性。

3. 绑定socket

1    ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
2    if(-1 == ret)
3    {
4        fprintf(stderr,"bind error:%s\n\a", strerror(errno));
5        close(sock_fd);
6        exit(1);
7    }

所需要头文件
#include <sys/types.h>
#include <sys/socket.h>
函数格式
int bind(int sockfd, struct sockaddr *addr, int addrlen);
函数功能
把套接字绑定到本地计算机的某一个端口上;
sockfd:待绑定的套接字描述符
addr:一个struct sockaddr *指针,指定要绑定给sockfd的协议地址。内容结构由前面的协议族决定。
addrlen:地址的长度
返回值
若成功,返回0,否则返回-1,错误信息存在errno中;

4. 开始监听

1    ret = listen(sock_fd, BACKLOG);
2    if (-1 == ret)
3    {
4        fprintf(stderr,"listen error:%s\n\a", strerror(errno));
5        close(sock_fd);
6        exit(1);
7    }

所需要头文件
#include <sys/types.h>
#include <sys/socket.h>
函数格式
int listen(int sockfd, int backlog);
函数功能
使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求,最大连接数量为backlog≤128;
sockfd:待监听的套接字描述符
backlog:最大可监听和连接的客户端数量
返回值
若成功,返回0,否则返回-1;

5. 阻塞,等待连接

1        addr_len = sizeof(struct sockaddr);
2        new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len);
3        if (-1 == new_fd)
4        {
5            fprintf(stderr,"accept error:%s\n\a", strerror(errno));
6            close(sock_fd);
7            exit(1);
8        }
1   

所需要头文件
#include <sys/types.h>
#include <sys/socket.h>
函数格式
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
函数功能
接受连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求;
当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求;
accept连接成功时,参数addr所指的结构体会填入所连接机器的地址数据;
sockfd:待监听的套接字描述符
addr:指向struct sockaddr的指针,用于返回客户端的协议地址
addrlen:协议地址的长度
返回值
若成功,返回一个由内核自动生成的一个全新描述字,代表与返回客户的TCP连接,否则返回-1,错误信息存在errno中;

6. 接收数据

 1    recv_len = recv(new_fd, recv_buf, 999, 0);
 2    if (recv_len <= 0)
 3    {
 4        fprintf(stderr, "recv error:%s\n\a", strerror(errno));
 5        close(new_fd);    
 6        exit(1);
 7    }
 8    else
 9    {
10        recv_buf[recv_len] = '\0';
11        printf("Get msg from client%d: %s\n", client_num, recv_buf);
12    }

所需要头文件
#include <sys/types.h>
#include <sys/socket.h>
函数格式
int recv(int sockfd, void *buf, size_t len, int flags);
函数功能
用新的套接字来接收远端主机传来的数据,并把数据存到由参数buf指向的内存空间;
sockfd:sockfd为前面accept的返回值,即new_fd,也就是新的套接字
buf:指明一个缓冲区
len:指明缓冲区的长度
flags:通常为0
返回值
若成功,返回接收到的字节数,另一端已关闭则返回0,否则返回-1,错误信息存在errno中;

7. 关闭socket

1    close(sock_fd);
2    exit(0); 

为了应对多个连接,并保证它们之间相互独立,实际编程中往往还要加入多进程fork()。
让子进程接收数据,父进程继续监听新的连接。

  • 客户机端:

1. 创建socket

1    sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP
2    if (-1 == sock_fd)
3    {
4        fprintf(stderr,"socket error:%s\n\a", strerror(errno));
5        exit(1);
6    }

2. 设置socket

1    memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear
2    server_addr.sin_family = AF_INET;
3    server_addr.sin_port = htons(PORT_NUMBER);

其中注意的是,这里设置的socket内容是指 希望连接的服务器IP和端口号信息,IP地址来自用户的输入,并转换格式得到。因此,这里的设置和服务器的设置,要保持内容上的一致。

1    ret = inet_aton(argv[1], &server_addr.sin_addr);
2    if(0 == ret)
3    {
4        fprintf(stderr,"server_ip error.\n");
5        close(sock_fd);
6        exit(1);
7    }

3. 连接

1    ret = connect(sock_fd, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr));    
2    if (-1 == ret)
3    {
4        fprintf(stderr,"connect error:%s\n\a", strerror(errno));
5        close(sock_fd);
6        exit(1);
7    }

所需要头文件
#include <sys/types.h>
#include <sys/socket.h>
函数格式
int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);
函数功能
用来请求连接远程服务器,将参数sockfd的socket连至参数serv_addr所指定的服务器IP和端口号上去;
sockfd:客户端的socket套接字
serv_addr:一个struct sockaddr类型的结构体指针变量,存储着远程服务器的IP与端口号信息
addrlen:结构体变量的长度
返回值
若成功,返回0,否则返回-1,错误信息存在errno中;

4. 发送

1            send_buf = send(sock_fd, send_buf, strlen(send_buf), 0);
2            if (send_buf <= 0)
3            {
4                fprintf(stderr,"send error:%s\n\a", strerror(errno));
5                close(sock_fd);
6                exit(1);
7            }

所需要头文件
#include <sys/types.h>
#include <sys/socket.h>
函数格式
int send(int sockfd, const void *buf, int len, int flags);
函数功能
用来发送数据给指定的远端主机;
sockfd:客户端的socket套接字
buf:指明一个缓冲区
len:指明缓冲区的长度
flags:通常为0
返回值
若成功,返回发送的字节数,否则返回-1,错误信息存在errno中

5. 关闭socket

1    close(sock_fd);
2    exit(0);

TCP完整例子:

service:

 1#include <stdio.h>
 2#include <unistd.h>
 3#include <sys/types.h>
 4#include <sys/socket.h>
 5#include <strings.h>
 6#include <string.h>
 7#include <ctype.h>
 8#include <arpa/inet.h>
 9
10#define SERV_PORT 9527
11
12int main(void)
13{
14    int sfd, cfd;
15    int len, i;
16    char buf[BUFSIZ], clie_IP[BUFSIZ];
17
18    struct sockaddr_in serv_addr, clie_addr;
19    socklen_t clie_addr_len;
20
21    /*创建一个socket 指定IPv4协议族 TCP协议*/
22    sfd = socket(AF_INET, SOCK_STREAM, 0);
23
24    /*初始化一个地址结构 man 7 ip 查看对应信息*/
25    bzero(&serv_addr, sizeof(serv_addr));           //将整个结构体清零
26    serv_addr.sin_family = AF_INET;                 //选择协议族为IPv4
27    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //监听本地所有IP地址
28    serv_addr.sin_port = htons(SERV_PORT);          //绑定端口号    
29
30    /*绑定服务器地址结构*/
31    bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
32
33    /*设定链接上限,注意此处不阻塞*/
34    listen(sfd, 64);                                //同一时刻允许向服务器发起链接请求的数量
35
36    printf("wait for client connect ...\n");
37
38    /*获取客户端地址结构大小*/ 
39    clie_addr_len = sizeof(clie_addr_len);
40    /*参数1是sfd; 参2传出参数, 参3传入传入参数, 全部是client端的参数*/
41    cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);           /*监听客户端链接, 会阻塞*/
42
43    printf("client IP:%s\tport:%d\n", 
44            inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), 
45            ntohs(clie_addr.sin_port));
46
47    while (1) {
48        /*读取客户端发送数据*/
49        len = read(cfd, buf, sizeof(buf));
50        write(STDOUT_FILENO, buf, len);
51
52        /*处理客户端数据*/
53        for (i = 0; i < len; i++)
54            buf[i] = toupper(buf[i]);
55
56        /*处理完数据回写给客户端*/
57        write(cfd, buf, len); 
58    }
59
60    /*关闭链接*/
61    close(sfd);
62    close(cfd);
63
64    return 0;
65}

 

client:

 1#include <stdio.h>
 2#include <unistd.h>
 3#include <string.h>
 4#include <sys/socket.h>
 5#include <arpa/inet.h>
 6
 7#define SERV_IP "127.0.0.1"
 8#define SERV_PORT 9527
 9
10int main(void)
11{
12    int sfd, len;
13    struct sockaddr_in serv_addr;
14    char buf[BUFSIZ]; 
15
16    /*创建一个socket 指定IPv4 TCP*/
17    sfd = socket(AF_INET, SOCK_STREAM, 0);
18
19    /*初始化一个地址结构:*/
20    bzero(&serv_addr, sizeof(serv_addr));                       //清零
21    serv_addr.sin_family = AF_INET;                             //IPv4协议族
22    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);    //指定IP 字符串类型转换为网络字节序 参3:传出参数
23    serv_addr.sin_port = htons(SERV_PORT);                      //指定端口 本地转网络字节序
24
25    /*根据地址结构链接指定服务器进程*/
26    connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
27
28    while (1) {
29        /*从标准输入获取数据*/
30        fgets(buf, sizeof(buf), stdin);
31        /*将数据写给服务器*/
32        write(sfd, buf, strlen(buf));       //写个服务器
33        /*从服务器读回转换后数据*/
34        len = read(sfd, buf, sizeof(buf));
35        /*写至标准输出*/
36        write(STDOUT_FILENO, buf, len);
37    }
38
39    /*关闭链接*/
40    close(sfd);
41
42    return 0;
43}

推荐阅读:

网络编程<一>

Linux I/O复用--epoll

Linux I/O复用——poll()

Linux I/O复用—select()

线程池网络服务

多线程网络服务

Socket网络编程

线程高级操作

Linux多线程编程

线程

进程间通信(IPC)

进程间通信(一)

进程间通信(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值