Linux网络编程

前面说的管道、消息队列、共享内存、信号、信号量,都是基于Linxux内核,无法实现多机通讯。

而网络编程中的Socket正好可以满足。现在网络上各种各样的服务大多是基于Socket来完成通信的。

网络编程四个要点:客户端的地址、客户端的端口、服务器的地址、服务器端口。

一.端口号的作用

一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等

这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。

实际上是通过“IP地址+端口号”来区 分不同的服务的。

端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。

二.TCP协议与UDP协议的区别(jizhu)

1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2.  TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3.  TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

4.  每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5.  TCP首部开销20字节;UDP的首部开销小,只有8个字节

6.  TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。

 三、字节序

小端字节序:将低序字节存储在起始地址。
大端字节序:将高序字节存储在起始地址。

网络字节序=大端字节序

例子:在内存中双字0x01020304的存储方式
地址 4000 4001 4002 4003
小端 04 03 02 01
大端 01 02 03 04

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

 

五、API介绍

1.socket函数

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
参数
1.domain:指明所使用的协议族。通常为AF_INET,表示互联网协议族(TCP/IP协议族);

AF_INET:IPv4因特网域
AF_INET6:IPv6因特网域
AF_UNIX:Unix域
AF_ROUTE:路由套接字
AF_KEY:密钥套接字
AF_UNSPEC:未指定
2.type参数指定socket的类型:

SOCK_STREAM:流式套接字提供可靠的、面向连接的通信流,它使用TCP协议,从而保证了数据传输的正确性和顺序性。
SOCK_DGRAM:数据报文式套接字定义了一种无连接的,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
SOCK_RAM:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
3.protocol 通常赋值0。

0选择type类型对应的默认协议
IPPROTO_TCP:TCP传输协议
IPPROTO_UDP:UDP传输协议
IPPROTO_STCP:STCP传输协议
IPPROTO_TIPC:TIPC传输协议
返回
成功的话返回Scoket描述符Sockfd,失败则返回-1。

 2.bind函数

 #include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:用于绑定IP地址和端口号到sockfd
参数
sockfd:Socket描述符
addr:指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族不同而不同。
addrlen:sockaddr结构体类型的长度大小
返回
成功返回0,失败返回-1,并设置errno

 //ipv4对应的是:
struct sockaddr {
sa_family_t sa_family;//协议族 char
sa_data[14];//IP+端口
}

同等替换:

/*进入/usr/include/目录,命令行grep "struct sockaddr_in {" * -nir 查找,*代表当前目录,n是显示行号,i是不区分大小写,r代表递归。查找到内容在linux/in.h  +184,+184代表184行,用vi进入查看*/
struct sockaddr_in
{
sa_family_t sa_family;//协议族
in_port_t sin_port;//端口
struct in_addr sin_addr;//IP地址结构体
unsigned char sin_zero[8]
//填充,没有实际意义,只是为跟sockaddr结构在内存中对齐,这样两者才能相互转换
}

地址转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//把字符串形式的”192.168.0.123”转为网络能识别的格式,即in_addr结构体类型 int inet_aton(const char *cp, struct in_addr *inp);
//把网络格式的ip地址转为字符串形式
char *inet_ntoa(struct in_addr in);
 

 3.监听函数

#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:设置能处理的最大连接数,listen函数只能用于服务器端,服务器进程不知道要与谁连接,因此,它不会主动要求与某个进程连接,只能监听是否有其他客户端进程与之连接,然后响应其连接请求,并对它做出处理。
参数
sockfd:socket描述符
backlog:在请求队列中允许的最大请求数。

4.连接函数 

 #include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:函数由tcp服务器调用,用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
参数
sockfd:socket描述符
addr:用于返回已连接的客户端的协议地址
addrlen:客户端地址长度。
返回
返回一个已连接的客户端套接字描述符,失败返回-1,并设置errno

5.数据收发 

#include <unistd.h>
//函数均返回读或写的字节个数,出错则返回-1。
ssize_t read(int sockfd, void *buf, size_t count);
ssize_t write(int sockfd, const void *buf,size_t count);
 

 第二套

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//包含3要素:套接字s,待发数据msg,数据长度len
//函数只能对处于连接状态的套接字使用,参数s为已建立好连接的套接字描述

//参数buf指向存放待发送数据的缓存区
//参数len为待发送数据的长度,参数flags为控制选项,一般设置为0

 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//包含3要素:套接字s,待发数据buf,数据长度len
//函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收
//数据并保存到参数buf所指定的缓冲区
//参数len则为缓冲区长度,参数flags为控制选项,一般设置为0

6.客户端connect函数 

 #include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:该函数用于绑定之后的client客户端,与服务器建立连接。
参数
sockfd:目的服务器的socket描述符
addr:服务器端的IP地址和端口号的地址结构指针
addrlen:地址长度常被设置为sizeof(struct sockaddr)
返回
成功返回0,失败返回-1,并设置errno

 7.关闭套接字

#include <unistd.h>
int close(int sockfd)
返回
成功返回0,失败返回-1,并设置errno 

8.字节序转换 

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//返回网络字节序的值
uint16_t htons(uint16_t hostshort);//返回网络字节序的值
uint32_t ntohl(uint32_t netlong);//返回主机字节序的值
uint16_t ntohs(uint16_t netshort);//返回主机字节序的值

h代表host,n代表net,s代表short(两个字节),l代表long(四个字节),通过上面四个函数来实现主机字节序与网络字节序之间的转换。

六、实现多消息发送

服务器代码:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
//       int socket(int domain, int type, int protocol);
//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

int main(int argc,char **argv)
{
        int s_fd;
        int c_fd;
        int mark=0;
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        char readBuf[128];
//      char *mes="I get your message";
        char mes[128]={0};
        int nread;
        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        if(argc != 3){
                printf("paran is not good");
                exit(-1);
        }
        //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);
        //2.bind
        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3.listen
        listen(s_fd,10);
        int clen = sizeof(struct sockaddr_in);
        while(1){
                //4.accept
                c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);
                if(c_fd == -1){
                        perror("accept");
                }
                mark++;
                printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));
                if(fork()==0){

                        if(fork()==0){
                        while(1){
                        sprintf(mes,"welcom No %d client",mark);
                        write(c_fd,mes,strlen(mes));
                        sleep(3);
                        }
                        }
                        //5.read
                        while(1){
                        memset(readBuf,0,sizeof(readBuf));
                        nread=read(c_fd,readBuf,128);
                        if(nread == -1){
                                perror("read");
                        }else{
                                printf("get message:%d,%s\n",nread,readBuf);
                        }
                        }
                        break;
                }

        }
        return 0;
}

客户端代码

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
//       int socket(int domain, int type, int protocol);
//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

int main(int argc,char **argv)
{
        int c_fd;
        struct sockaddr_in c_addr;
        char readBuf[128];
        //      char *mes="mes from clint";
        char mes[128]={0};
        int nread;
        memset(&c_addr,0,sizeof(struct sockaddr_in));

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

        //1.socket
        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);

        //2.connet
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1)
        {
                perror("connect");
                exit(-1);
        }
        while(1){
                //3.write
                if(fork()==0){
                        while(1){
                        memset(mes,0,sizeof(mes));
                        printf("input: ");
                        gets(mes);
                        write(c_fd,mes,strlen(mes));
                }
                }
                //4.read
                while(1){
                memset(readBuf,0,sizeof(readBuf));
                nread=read(c_fd,readBuf,128);
                if(nread == -1){
                        perror("read");
                }else{
                        printf("get message from server:%d,%s\n",nread,readBuf);
                }
        }
        }

        return 0;
}

 服务器:

 客户端1

 客户端2

 学习笔记,仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值