计算机网络概述
Linux系统也与网络密不可分,无论是服务器开发,还是嵌入式应用等领域,都需要通过网络进行数据传递。Linux网络编程一般通过socket(套接字)
接口实现。
关于网络的概述我就不细讲了,看了晕头转向的,在此推荐这个老哥的博文
1-1:网络初识之了解什么是协议以及TCP/IP协议
1-2:网络初识之了解OSI和TCP/IP及网络分层(物理层,数据链路层,网路层,传输层,应用层)
1-3:网络初识之网络传输的基本流程TCP首部,IP首部,MAC地址,IP地址等
补充几点:
网络结构模式:C/S B/S
网络结构模式分为两种,一种为客户机(client)/服务器(server)模式,即C/S模式,此种模式需要在进行通信的两端分别架设客户机和服务器;另一种为浏览器(browser)/服务器模式,即B/S模式,是WEB(World Wide Web)兴起后的一种网络结构模式,客户机只需安装浏览器,便可与服务器进行交互。
socket编程基础
在Linux系统中,socket可用于表示进程间进行网络通信时使用的特殊文件类型,也可用于表示socket编程中的一系列接口。socket本意为“插座”,常被称为套接字。当使用socket进行通信时,进程会先生成一个socket文件,之后再通过socket文件进行数据传递。
Linux系统中将socket具体化为一种文件只是为了给用户提供与操作普通文件相同的接口,使用户可以通过文件描述符来引用和操作套接字。实际上,socket的本质为内存缓冲区形成的伪文件,与管道本质类似,不同的是,socket多用于与网络相关的进程通信。
在网络通信中,socket一定是成对出现的。socket的缓冲区分为读写两个部分,每个socket都能接收和发送文件,一端的发送缓冲区会对应另一端的接收缓冲区。
对用户来说,不必了解socket文件的具体构成,只需掌握与socket相关的接口即可。socket接口位于应用层与TCP/IP协议族之间,是基于软件的抽象层,它与体系结构中各层的关系如图10-5所示。
socket编程接口
Linux系统中常用的socket网络编程接口有socket()、bind()、listen()、accept()、connect()、send()、recv()、close(),
其中connect()与send()为客户端专用接口
,bind()、listen()、accept()及recv()为服务器端专用接口
,socket()与close()则由服务器与客户端共用。
socket()
bind()
如亦可使用以下语句,定义一个struct sockaddr_in
类型的结构体:
struct sockaddr_in servaddr; //结构体定义
bzero(&servaddr, sizeof(servaddr)); //结构体清零
servaddr.sin_family = AF_INET; //设置地址类型为AF_INET
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置网络地址为INADDR_ANY
servaddr.sin_port = htons(85); //设置端口号为85
listen()
accept()
connect()
send()
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
recv()
close()
socket 通信流程
根据进程在网络通信中使用的协议,可将socket通信方式分为两种:一种是面向连接、基于TCP协议的通信;另一种是面向无连接、基于UDP协议的通信。
网络编程相关知识
网络字节序—大小端
Linux系统中提供了一些用于字节序转换的函数,这些函数存在于函数库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);
ip转换函数
sockaddr数据结构
更加详细看:2-1:套接字(Socket)编程之必备知识
socket 网络通信实例
C/S TCP
服务端程序:tcpserver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80 //最大数据长度
#define SERV_PORT 6666 //服务器端口号
int main(void)
{
struct sockaddr_in servaddr, cliaddr; //定义服务器与客户端地址结构体
socklen_t cliaddr_len; //客户端地址长度
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
//创建服务器端套接字文件
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//初始化服务器端口地址
bzero(&servaddr, sizeof(servaddr)); //将服务器端口地址清零
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//将套接字文件与服务器端口地址绑定
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//监听,并设置最大连接数为20
listen(listenfd, 20);
printf("Accepting connections ...\n");
//接收客户端数据,并处理请求
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = recv(connfd, buf, MAXLINE, 0);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
send(connfd, buf, n, 0);
//关闭连接
close(connfd);
}
return 0;
}
tcpclient.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 6666
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));
//发送数据
send(sockfd, str, strlen(str), 0);
//接收客户端返回的数据
n = recv(sockfd, buf, MAXLINE, 0);
printf("Response from server:\n");
//将客户端返回的数据打印到终端
write(STDOUT_FILENO, buf, n);
//关闭连接
close(sockfd);
return 0;
}
C/S UDP
udpserver.c //服务器端
#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <ctype.h>
#define MAXLINE 80 //最大数据长度
#define SERV_PORT 6666 //服务器端口号
int main(void)
{
struct sockaddr_in servaddr, cliaddr; //定义服务器与客户端地址结构体
socklen_t cliaddr_len; //客户端地址长度
int sockfd; //服务器socket文件描述符
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);//创建服务器端套接字文件
//初始化服务器端口地址
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, (struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Accepting connections ...\n");
//数据传输
while (1) {
cliaddr_len = sizeof(cliaddr);
//接收数据
n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr*)&cliaddr,
&cliaddr_len);
if (n == -1)
perror("recvfrom error");
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
//服务器端操作,小写转大写
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr,
sizeof(cliaddr));
if (n == -1)
perror("sendto error");
}
close(sockfd);
return 0;
}
udpclient.c //客户端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
int sockfd, n;
char buf[MAXLINE];
sockfd = socket(AF_INET, SOCK_DGRAM, 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);
//发送数据到客户端
while (fgets(buf, MAXLINE, stdin) != NULL) {
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr,
sizeof(servaddr));
if (n == -1)
perror("sendto error");
//接收客户端返回的数据
n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
if (n == -1)
perror("recvfrom error");
//将接收到的数据打印到终端
send(STDOUT_FILENO, buf, n, 0);
}
close(sockfd);
return 0;
}
2-3:套接字(Socket)编程之UDP通信
2-4:套接字(Socket)编程之TCP通信
socket本地通信
服务器端 dmserve.c
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#define QLEN 10
//创建服务器进程,成功返回0,出错返回小于0的errno
int serv_listen(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;
//创建本地domain套接字
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
//删除套接字文件,避免因文件存在导致bind()绑定失败
unlink(name);
//初始化套接字结构体地址
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}
if (listen(fd, QLEN) < 0) { //告知内核这是一个服务器进程
rval = -3;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(rval);
}
int serv_accept(int listenfd, uid_t *uidptr)
{
int clifd, len, err, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
return(-1);
//从调用地址获取客户端的uid
len -= offsetof(struct sockaddr_un, sun_path); //获取路径名长度
un.sun_path[len] = 0; //为路径名字符串添加终止符
if (stat(un.sun_path, &statbuf) < 0) {
rval = -2;
goto errout;
}
if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -3; //若返回值为-3,说明这不是一个socket文件
goto errout;
}
if (uidptr != NULL)
*uidptr = statbuf.st_uid; //返回uid的调用者指针
//到此成功获取路径名
unlink(un.sun_path);
return(clifd);
errout:
err = errno;
close(clifd);
errno = err;
return(rval);
}
int main(void)
{
int lfd, cfd, n, i;
uid_t cuid;
char buf[1024];
lfd = serv_listen("foo.socket");
if (lfd < 0) {
switch (lfd) {
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}
cfd = serv_accept(lfd, &cuid);
if (cfd < 0) {
switch (cfd) {
case -3:perror("not a socket"); break;
case -2:perror("a bad filename"); break;
case -1:perror("accept"); break;
}
exit(-1);
}
while (1) {
r_again:
n = read(cfd, buf, 1024);
if (n == -1) {
if (errno == EINTR)
goto r_again;
}
else if (n == 0) {
printf("the other side has been closed.\n");
break;
}
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, n);
}
close(cfd);
close(lfd);
return 0;
}
客户端 dmclient.c
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
//创建客户端进程,成功返回0,出错返回小于0的errno
int cli_conn(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;
//创建本地套接字domain
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
//使用自定义地址填充socket地址结构体
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
unlink(un.sun_path); //避免因文件已存在导致的bind()失败
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}
//使用服务器进程地址填充socket地址结构体
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
if (connect(fd, (struct sockaddr *)&un, len) < 0) {
rval = -4;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(rval);
}
int main(void)
{
int fd, n;
char buf[1024];
fd = cli_conn("foo.socket"); //套接字文件为foo.socket
if (fd < 0) { //容错处理
switch (fd) {
case -4:perror("connect"); break;
case -3:perror("listen"); break;
case -2:perror("bind"); break;
case -1:perror("socket"); break;
}
exit(-1);
}
while (fgets(buf, sizeof(buf), stdin) != NULL) {
write(fd, buf, strlen(buf));
n = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);
}
close(fd);
return 0;
}
脚下留心:出错处理函数封装
面将对网络编程中的常用接read()write()等函数进行再次封装,为其添加出错处理功能,并将这些新函数的声明和定义分别保存在文件wrap.h 和 wrap.c
#ifndef __WRAP_H_
#define __WRAP_H_
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
void Bind(int fd, const struct sockaddr *sa, socklen_t salen);
void Connect(int fd, const struct sockaddr *sa, socklen_t salen);
void Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
void Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
static ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
//socket()
int Socket(int family, int type, int protocol)
{
int n;
if ((n = socket(family, type, protocol)) < 0)//若socket调用失败
perr_exit("socket error");
return n;
}
//bind()
void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (bind(fd, sa, salen) < 0) //若bind()调用失败
perr_exit("bind error");
}
//listen()
void Listen(int fd, int backlog)
{
if (listen(fd, backlog) < 0) //若listen()调用失败
perr_exit("listen error");
}
//connect()
void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
if (connect(fd, sa, salen) < 0) //若connect()调用失败
perr_exit("connect error");
}
//accept()
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if ((n = accept(fd, sa, salenptr)) < 0) { //若accept()调用失败
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
//close()
void Close(int fd)
{
if (close(fd) == -1) //关闭socket失败
perr_exit("close error");
}
//read()
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ((n = read(fd, ptr, nbytes)) == -1) { //读取数据失败
if (errno == EINTR)
goto again; //重读fd
else
return -1;
}
return n;
}
//write()
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ((n = write(fd, ptr, nbytes)) == -1) { //写数据出错
if (errno == EINTR)
goto again; //重新写入
else
return -1;
}
return n;
}
void perr_exit(const char *s)
{
perror(s);
exit(1);
}